The Simplest Guide to Using Async/Await with forEach() in JavaScript (with Examples)

Using async/await with forEach() does not need to be a nightmare! Here are 4 solutions to your problem.

By Salvor Pirenne

January 5th, 2022

image

Ask any JavaScript developer, and they will all agree:

  • async/await is frigging insane! It is that good.

  • Except when you decide to use it inside a forEach(). Then it can get real ugly real fast.

Your search for any query around using async/await with forEach() in JavaScript - one of the first results would be a StackOverflow question.

Source Search query: https://www.google.com/search?q=using+Async%2FAwait+with+forEach%28%29+in+JavaScript

Source Search query: https://www.google.com/search?q=using+Async%2FAwait+with+forEach%28%29+in+JavaScript

There are countless developers who have fallen prey to this beast and have struggled to sort out the mess. And that is exactly what this guide will help you overcome.

Once you have completed this guide, never again will you need to come across Google search results that look like this:

Source: https://www.youtube.com/watch?v=4lqJBBEpjRE

Source: https://www.youtube.com/watch?v=4lqJBBEpjRE

So, without further delay, let’s brew a fresh cup of coffee and get started!

What issue does async/await pose in a JavaScript code with forEach()

The problem?

Let us look at some code to understand the problem:

const subscribedUsers = await this.fetchYesterdaysSubscriptions();

await subscribedUsers.forEach(async (user) => {
  await sendPromoCode(user);
});

await sendEmailToAdmin("All new subscribers have been sent the promo code");

The intent of the code is fairly straight-forward:

  1. subscribedUsers waits for the result of the function that fetches my new subscribers from yesterday.
  2. To each user in the subscribedUsers array, I am sending a promotional code for my product.
  3. Once each user has been sent the promo code, the system sends a mail to me informing me that the action has been successfully completed.

This is what you would expect this code block to do.

It is also a fairly common use case. You would be using codes like this for a bunch of different desired outcomes. So, chances are, you have already run into such problems earlier.

But there are multiple things wrong with this code, and this is what is actually happening behind the scenes:

  • There is no error handling for the promo code sending process. So, if one of the iteration throws an error, the error wouldn’t be caught. Not good! (But, we will ignore the error-handling for the time being — _one problem at a time, my friend. One problem at a time.)

  • You add a console.log with a currentTime directive in there, and you will find that each iteration is happening in parallel. But, the way the code is written, you would have expected them to happen in series — one after another.

  • The loop finishes iterating before any of the promo code emails have been sent. Yes, the process of sending the emails has started, but the code is not waiting for the process to resolve before moving on. Which is not how you wanted the code to flow.

  • The admin is sent the confirmation email even though none of the promo codes have been sent yet. Maybe some of them will fail, maybe they all will. The admin will never know because he was sent the mail independent of how the promise resolved itself.

The reason behind this problem:

To put things in an extremely simplistic manner, [Array.prototype.forEach](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach) was never designed for asynchronous code.

It was not suitable for promises, even when there were [Promise.then()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then) in all the code you would see around you. And now, in the world of async/await, it is still not suitable for async/await.

So, how do we solve it?

There are a few different ways of solving this issue.

The most basic and simplest answer is to not use forEach() with your async/await.

After all, if forEach() is not designed for asynchronous operations, why expect it to do something it isn't made to do naturally.

For....of will work perfectly for any usecase you may have where you want to use forEach.

But, what if you preferred the forEach approach. Okay, Mr. Picky! We will have a solution for that as well. But, fair warning --- you would be better suited with the other approaches!

However, lets digress a bit. Hit the breaks on solving the problem of getting your async/await to work perfectly in a forEach loop (using both methods --- for...of as well as forEach).

Let us take a moment to get our basics right, and briefly understand:

  • What exactly is Async/Await in JavaScript?

  • Why is it important for JavaScript developers to be familiar with async/await?

  • How do you use async/await in your JavaScript code?

  • Should you use async/await over Promises in your code? Which is better?

Let’s get on with it then!

What is Async/Await in JavaScript and Why Should You Care?

What is async/await?

Async/await allows your asynchronous JavaScript code to execute without blocking the main thread.

The async keyword specifies the function as an asynchronous operation. This makes sure that the function will always return a promise.

Source: https://javascript.info/async-await

Source: https://javascript.info/async-await

In this function, return 1 is essentially the same as return Promise.resolve(1)

The await keyword specifies which function should be awaited. Please note: the await keyword works (or can be used) only inside of an async function.

Please note: With the latest updates, most modern browsers will let you use top-level awaits, if you are working in a module. But for now, let’s keep things simple.

All the await keyword does is pause the code execution wherever it is used until the promise it is used with is resolved.

This codeblock from javascript.info will clear things up:

So, any code fragment that comes after the await line waits its turn and is executed only when the promise has been resolved.

Let me reiterate. The keyword await will literally suspend any further execution of the function until the promise has been resolved. It is then (and only then), that the function resumes, and when it does, it resumes with the result we received from the promise.

Why should you care?

  • It is a more elegant way of writing code.

  • It is more efficient.

  • It is less resource-heavy. Since the function execution has been suspended, the JavaScript engine isn’t using any CPU resources for these particular functions and is free to execute other tasks in the meantime.

Your code gets the benefit of following a more elegant syntax of code for getting the result from a promise than promise.then(), and you (as a programmer) are left with a syntax that is easier and cleaner to read and write.

How to Use Async/Await in JavaScript

There are multiple great resources on how you can use async/await in your javaScript code/projects.

Here is one by Catalin’s Tech that I believe captures the essence perfectly.

Here is one by Basti Ortiz.

Both resources have been linked at the end of the story under suggested resources.

And since I’m a huge fan of fireship tutorials in their ability to explain complex topics in a beginner-friendly manner, here is a short video from Jeff Delaney (12 mins).

In contrast, other async/await videos you will find on YouTube are anywhere between 40 mins and 1.5 hours long.

Why do I include fireship tutorial videos over other examples? Just look at the comments:

Should You Choose Async/Await Over Promises?

It is a highly personal choice, and there are people on both sides of the aisle here.

There are those who swear by the amazing value async/await adds to your code over using plain old Promises. And then there are those who think async/await was spawned in the fires of hell.

Here is one such point many developers make against using async/await:

Are they right or wrong? You will have to make that call on a case-to-case basis, depending on what you are doing with your code.

But, speaking from personal experience, I am extremely indifferent to whether I am using async/await or Promise.then(). I have used both in my codes, and I find them both to be quite easy to use.

But, are there scenarios where I prefer async/await? Yes. When I end up tumbling down a rabbit hole of thens and callbacks. I find async/await to be a more cleaner solution in such cases.

When you come back to your code a few weeks later, or when you have another developer working on the same codebase, cleaner, neater and shorter code does have its advantage.

The bottomline? If clean and crisp code matters to you, do choose async/await. Your code looks different, cleaner, neater, and shorter. And the error handling simplicity improves.

But, if you were thinking of choosing async/await over Promise.then() because of some major performance enhancements, you would find yourself disappointed.

Async/Await essentially does the same thing that your Promise.then() code blocks do.

So, yes, your code is going to look different, but the logic flow and capabilities will stay the same.

Alright then. Now that we have briefly touched upon all there is to know about async/await, let’s get back to the matter at hand, and try fixing the problem we ran into with our async/await usage in forEach() earlier.

How to fix async/await usage with forEach() in JavaScript?

Alright.

The promo-code sending loop we saw earlier. Let’s fix that now.

#1. The for…of solution

const subscribedUsers = await this.fetchYesterdaysSubscriptions();

for (const user of subscribedUsers) {
  await sendPromoCode(user);
}

await sendEmailToAdmin("All new subscribers have been sent the promo code");

This loop will wait for one promo-code email to be sent before moving on with the next iteration.

PS: The awesome guys at Airbnb released a JavaScript Style guide. It follows and recommends what they call “a mostly reasonable approach to JavaScript”. The style guide is available at github as well. It is extremely popular, and is followed by thousands of JavaScript developers worldwide. If you weren’t aware of it, I would highly recommend taking a look at it. Anyway, the reason I bring up the Airbnb style guide is for the simple fact that it recommends not using for…of for web apps since it requires a large polyfill. If you prefer, you can use an Array.reduce() as well to achieve the same effect.

#2. The Array.reduce() solution

May the gods at Airbnb be happy with this solution!

const subscribedUsers = await this.fetchYesterdaysSubscriptions();

await subscribedUsers.reduce(async (referencePoint, user) => {
  // Check for execution status of previous iteration, i.e. wait for it to get finished
  await referencePoint;
  // Process the current iteration
  await sendPromoCode(user);
}, Promise.resolve());

await sendEmailToAdmin("All new subscribers have been sent the promo code");

We are used to using a second argument as an output from the previous code execution — a summary of how things went, if you may. But, in this particular case, things are a bit different.

We are using the argument — referencePoint — just as a means to pass the promise from the previous iteration’s callback to the next iteration.

This helps our code to wait for the previous iteration to finish before it starts processing the next one.

I’m not a huge fan of this approach.

While there is nothing wrong with it, and it behaves almost exactly the same as the for...of example we saw in solution #1, I find this solution to be:

  • tedious

  • hard to read

  • slightly confusing — especially for a beginner

But, this is the approach recommended by the Airbnb JavaScript style guide. Probably because it will reduce your browser bundle size since it doesn’t use iterations the way for…of does.

Some browsers do require a polyfill when your code has iterators, and polyfill does indeed pack a punch. Having said that, if I had to choose between bundle size and my convenience, Hello convenience! 😜

The traditional for approach, as well as the Array.reduce() approach we just looked at, processes your code in series. It sends one promo mail, waits for that promise to get resolved, then moves on to the next user.

But in a lot of cases (for example, this one itself), the order in which the loop is iterated through isn’t important.

You just want each user to receive a promo code independently.

So, instead of a series-processing, it is quicker to cycle through all new subscribers in parallel. Which brings us to our next solution in the list.

#3. The Array.map() solution

const subscribedUsers = await this.fetchYesterdaysSubscriptions();

await Promise.all(
  subscribedUsers.map(async (user) => {
    await sendPromoCode(user);
  })
);

await sendEmailToAdmin("All new subscribers have been sent the promo code");

This solution is elegant since it starts the process of sending the promo code to all users at once, and at the same time. But, it will wait for every single one of them to finish executing before it will move on to send the email to admin.

And now, let us finally move on to the show stopper for the night.

Using async/await with forEach()

#4. async/await, with forEach()…done the right way

Spoiler alert: It is horrible! Just use #1 or #3. I am not a huge fan of #2 (sorry Airbnb masters), and this one is…just…the…worst!

const subscribedUsers = await this.fetchYesterdaysSubscriptions();

async function customForEachFunction(arrayInput, callback) {
  arrayInput.forEach(async (item) => {
    await callback(item, arrayInput);
  });
}

const forEachSolution = async () => {
  await customForEachFunction(subscribedUsers, async (user) => {
    await sendPromoCode(user);
  });
  await sendEmailToAdmin("All new subscribers have been sent the promo code");
};

forEachSolution();

That’s disgusting!

It may work, but that doesn’t make it any less disgusting.

Conclusion

Don’t try to make something do what it isn’t supposed to do.

forEach() was never designed to work for asynchronous code. So if you try to make it do that, you will need to do some extra legwork just to make it happen.

And what is the point of doing all that when you have better, simpler, easier means of achieving the same output.

Over to you now.

  • Have you run into an issue with async/await because you did not realise you were using it with a forEach()?

  • What did you break during that time, as you tried to figure things out?

What more would you have wanted me to cover here? How do you go about using async/await in an iterable scenario? Let me know in the comments!

Suggested Resources:



Continue Learning