Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save anonimusprogramus/120c651d387a585d56d8617076cc9882 to your computer and use it in GitHub Desktop.

Select an option

Save anonimusprogramus/120c651d387a585d56d8617076cc9882 to your computer and use it in GitHub Desktop.
Do not use forEach with async-await

Do not use forEach with async-await

TLDR: Use for...of instead of forEach in asynchronous code.

The problem

Array.prototype.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.getWinners();

players.forEach(async (player) => {
  await givePrizeToPlayer(player);
});

await sendEmailToAdmin('All prizes awarded');

What's wrong with it?

  • The promises returned by the iterator function are not handled. So if one of them throws an error, the error won't be caught. (In Node 10, if no unhandledrejection listener has been registered, that will cause the process to crash!)
  • Because forEach does not wait for each promise to resolve, all the prizes are awarded in parallel, not serial (one by one).
  • So the loop actually finishes iterating before any of the prizes have finished been awarded (but after they have all started being awarded).
  • As a result, sendEmailToAdmin() sends the email before any of the prizes have finished being awarded. Maybe none of them will end up being awarded (they might all throw an error)!

So how should we write this?

Process each player in serial

Fortunately if your language has async-await then it will also have the for...of construction, so you can use that.

for (const player of players) {
  await givePrizeToPlayer(player);
}

This loop will wait for one prize to be awarded before proceeding to the next one.

You could also use a traditional for(...;...;...) here but that is more verbose than we need.

Note: The airbnb style guide recommends not using for...of for web apps at the current time (2018), because it requires a large polyfill. If you are working in the browser, use the traditional for mentioned above, or Array.reduce() described below.

Process all the players in parallel

await Promise.all(players.map(async (player) => {
  await givePrizeToPlayer(player);
}));

This will start awarding all the prizes at once, but it will wait for them all to complete before proceeding to sendEmailToAdmin().

Process each player in serial, using Array.prototype.reduce

Some people recommend this approach as more functional, but personally I find the for-of loop is clearer.

await players.reduce(async (a, player) => {
  await a;
  await givePrizeToPlayer(player);
}, Promise.resolve());

This has more or less the same behaviour as the for...of above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment