Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async/await in foreach not waiting [duplicate]

I'm trying to use async and await in a function that uses a forEach loop. Annoyingly I can't get it to work. What should happen is it takes an array of event docs, loops through them, adds in some extra data, then pushes them to the events array. This events array is then returned from the original function. Here is my code:

async function getEvents() {
  ...
  var events = []
  await addExtrasToDocsForUser(docs, currentUserId, events)
  return events

}


var addExtrasToDocsForUser = (docs, currentUserId, events) => {
    return docs.forEach(async (eventDoc) => {
        const event = await addExtrasToDocForUser(eventDoc, currentUserId)
        events.push(event)
    })
}

What actually happens is the getEvents() function returns events as an empty array before the forEach loop has completed. How do I fix this?

like image 895
Tometoyou Avatar asked Feb 05 '19 10:02

Tometoyou


People also ask

Can async-await be used inside forEach provide example?

forEach is not designed for asynchronous code. (It was not suitable for promises, and it is not suitable for async-await.) For example, the following forEach loop might not do what it appears to do: const players = await this.

What happens if you use await inside a loop and what are the alternatives?

When evaluate for loop, we have await promise inside the async function, the execution will pause until the await promise is settled. So, you can think of that the files are read one by one in a determined order. Sometimes, we really need the the async functions to be executed in a sequential order.

How do I use async-await inside for loop?

You need to place the loop in an async function, then you can use await and the loop stops the iteration until the promise we're awaiting resolves. You could also use while or do.. while or for loops too with this same structure. But you can't await with Array.

Does await wait for all promises?

If a Promise is passed to an await expression, it waits for the Promise to be fulfilled and returns the fulfilled value.


2 Answers

Basically, this is what happens inside forEach:

Array.prototype.forEach = function (callback) {
  for (let index = 0; index < this.length; index++) {
    callback(this[index], index, this);
  }
};

Actually, the real implementation is the following, but the bottom line is that we are not waiting for the callback to be done, so using a function that returns a promise won't wait for the promise to resolve everytime.

Your code is not complete and verifiable so I can't be sure that the following works, but it probably should act as you'd expect:

const addExtrasToDocsForUser = async (docs, currentUserId, events) => {
  for (let i=0; i<docs.length; i++) {
    const event = await addExtrasToDocForUser(docs[i], currentUserId);
    events.push(event);
  }
  return;
}

You might also want to check this CodeBurst article on foreach + async/await

like image 84
Nino Filiu Avatar answered Oct 15 '22 12:10

Nino Filiu


Use Promise.all and map get all inner promises and return a single one that awaits them all:

const addExtrasToDocsForUser = async (docs, currentUserId, events) => {
    return Promise.all(docs.map(async (eventDoc) => {
        const event = await addExtrasToDocForUser(eventDoc, currentUserId)
        events.push(event)
    }));
}
like image 37
jo_va Avatar answered Oct 15 '22 12:10

jo_va