Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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();

// BAD
await 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

If the order doesn't matter, it may be quicker to 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:

await players.reduce(async (a, player) => {
  // Wait for the previous item to finish processing
  await a;
  // Process this item
  await givePrizeToPlayer(player);
}, Promise.resolve());

(We are using the accumulator a not as a total or a summary, but just as a way to pass the promise from the previous item's callback to the next item's callback, so that we can wait for the previous item to finish being processed.)

This has pretty much the same behaviour as the for...of above, but is slightly harder to read.

However it is recommended by the Airbnb style guide because it can reduce the browser bundle size. for...of requires iterators, and some browsers require a polyfill for iterators, and that polyfill is quite large. You can decide on the trade-off between bundle size and developer convenience.

So which array functions can I use?

TLDR: Only map(), reduce(), flatMap() and reduceRight() if used correctly

async-await works naturally with for loops and while loops, because they are written in the original function body.

But when you call out to another function, it can only work with async-await if it returns a promise.

That is why we can use .reduce() and .map() above, because in both cases we return a promise (or an array of promises) which we can await. (And importantly in the reduce case, each invocation of the callback function waits for the previous promise to resolve, to ensure sequential processing.)

But most array functions will not give us a promise back, or allow a promise to be passed from one call to the next, so they cannot be used asynchronously. So, for example, you can not use asynchronous code inside array.some() or array.filter():

// BAD
const playersWithGoodScores = await players.filter(async (player) => {
  const score = await calculateLatestScore(player);
  return score >= 100;
});

It might look like that should work but it won't, because filter was never designed with promises in mind. When filter calls your callback function, it will get a Promise back, but instead of awaiting that promise, it will just see the promise as "truthy", and immediately accept the player, regardless of what their score will eventually be.

You may be able to find a library of array functions that can work asynchronously, but the standard array functions do not.

@Misos14
Copy link

Misos14 commented Nov 25, 2021

Thank you 🫀

@prasanna214
Copy link

prasanna214 commented Dec 2, 2021

Thank you.

@jamietanna
Copy link

jamietanna commented Dec 7, 2021

This has been causing me problems for about a month (deploying AWS Lambda), and I've been through many attempts at solving it before this, thanks 🙌

@alan-dev-hk
Copy link

alan-dev-hk commented Dec 7, 2021

Nice! Thanks

@adrianbona
Copy link

adrianbona commented Dec 16, 2021

I love you!

@darwinPro
Copy link

darwinPro commented Dec 16, 2021

Thanks 👍

@martenmatrix
Copy link

martenmatrix commented Dec 20, 2021

Great explanation!

@faical23
Copy link

faical23 commented Jan 13, 2022

thanku so much u solve my problem

@cris-riffo
Copy link

cris-riffo commented Jan 25, 2022

Legend!

@gwilczynski
Copy link

gwilczynski commented Feb 8, 2022

🙌

@thiegomoura
Copy link

thiegomoura commented Feb 11, 2022

Obrigado pela contribuição!

@Rmlyy
Copy link

Rmlyy commented Mar 21, 2022

Thanks!

@glnicolas
Copy link

glnicolas commented Mar 29, 2022

Thanks dude!!

@ChinaCappuccino
Copy link

ChinaCappuccino commented Mar 31, 2022

Thanks ❤️🥺👉🏾👈🏾

@ulnk
Copy link

ulnk commented Apr 7, 2022

👍

@JosephRosenfeld
Copy link

JosephRosenfeld commented Apr 19, 2022

Super cool, thanks for writing this up!

@fzn0x
Copy link

fzn0x commented Apr 24, 2022

Thanks man!

@recorder12
Copy link

recorder12 commented May 6, 2022

Thank you! It is very helpful for me! 👍

@shakhzodnematullokh
Copy link

shakhzodnematullokh commented May 18, 2022

good

@victorekpo
Copy link

victorekpo commented Jun 1, 2022

Thank you!

@LaraAcuna
Copy link

LaraAcuna commented Jun 9, 2022

Very useful, thanks for sharing this!

@thafseerahamed
Copy link

thafseerahamed commented Jun 11, 2022

Thank you...
You really saved me...

@sydinh
Copy link

sydinh commented Jun 14, 2022

Thank you 😍

@jordantorreon
Copy link

jordantorreon commented Jul 2, 2022

Thank you! you save me

@vishnukumarps
Copy link

vishnukumarps commented Jul 9, 2022

Thank you for this info

@jiaowochunge
Copy link

jiaowochunge commented Jul 14, 2022

nice work

@sonnylazuardi
Copy link

sonnylazuardi commented Jul 29, 2022

Thank you this is really helpful

@jjayaraman
Copy link

jjayaraman commented Aug 5, 2022

Thanks, it saved me ....

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