James Perrone
James Perrone

Reputation: 107

Async/Await inside Reduce

I am trying to use async / await with the reduce method. But somehow only the first iteration of the loop works. The papers object skips the await once the first reduce iteration is done. Is it possible to create an object using reduce while gathering it's content from a promise while also await for the entire process?

Reduce function:

const papers = await page.concepts.reduce(async (acc, cur) => {
  acc[cur.paperID] = await getPageData(`papers/${ cur.paperID }`, 'md')
  return acc
}, {})

Note: getPageData returns a promise.

Upvotes: 1

Views: 2483

Answers (3)

nonopolarity
nonopolarity

Reputation: 151036

Since the async function returns a promise, so you would have to expect your accumulator is a promise. So you would do something like this:

console.log("Program starting at", Date());

function delayedResolver(a, ms) {
    return new Promise(function(resolve, reject) {
        setTimeout(() => {
            console.log("resolving at", Date());
            resolve(a);
        }, ms);
    });
}

arr = [1, 3, 5];

r = arr.reduce(async function(a, b) { 
      return await delayedResolver(a, 3000).then(v => v + b); 
    }, Promise.resolve(0));

console.log(r);

r.then(v => console.log("final value", v, "at", Date()));

You have to treat the accumulator as a promise, and return a new promise each time.

Note that the async function returns a promise and the reduce immediately applies the async function to the next pair of (accumulator, entry), so all these promises returned by the async function are created almost instantly, so that's why the promises resolve all after 3 seconds, not one by one.

Upvotes: 1

hawks
hawks

Reputation: 931

You can create an array of promises and then use Promise.all to resolve them. If any request fails Promise.all will stop processing the remaining requests.

const papersPromises = page.concepts.map(cur => 
    getPageData(`papers/${ cur.paperID }`, 'md')
)

const result = await Promise.all(papersPromises)
// result will be an array with the results

Upvotes: 2

Max
Max

Reputation: 4739

This is one way to fold array into an object, but, naturally, it will not handle async functions and reduce itself. Also, reduce doesn't return a Promise to await for

You can solve your case with Promise.all and Object.fromEntries

// map concepts to promises which will resolve to page data for each concept
// and await for them all at once (will run in parallel)
const pageResponses = await Promise.all(page.concepts.map(concept => getPageData(`papers/${concept.paperID}`, 'md')))
// construct object where keys are concept.paperIDs and values are pageDatas
const papers = Object.fromEntries(
  page.concepts
    .map((concept, i) => ([concept.paperID, pageResponses[i]]))
)

if your runtime doesn't support Object.fromEntries (and you are not using polyfill), you can do the last part like this

const papers = page.concepts.reduce((acc, concept, index) => acc[concept.paperID] = pageResponses[index], {})

Upvotes: 1

Related Questions