Why Async/Await Inside forEach Is a Bad Idea
Tags: javascript, beginners, node, webdev
Published: 2022-10-04 13:07:52

*This article was originally published at https://maximorlov.com/async-await-inside-foreach/*

Did you ever run several asynchronous functions and pushed the results to an array, only to find out it's empty?

![A broken code example where forEach is used on an array of urls to fetch then push the responses to an array. Right after the forEach the array is logged which shows up as empty.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c197ln5lzt8ctrltqjx3.png)

The values are there (you've console.log'ed them) and they're being pushed, how can the array be empty?!

So confusing.. 😕

Waiting for asynchronous functions to finish *before* running some other code seems like an impossible task.

But that's only because you haven't learned yet what tools to use.

When you learn how to **combine Promise.all and .map into a powerful combo**, you'll realize it's easier than you thought. 😎

## Why async/await inside forEach doesn't do what you think it does
When you need to run an asynchronous operation for each element in an array, you might naturally reach out for the `.forEach()` method.

For instance, you might have an array of user IDs and for each user ID, you want to fetch the user and push the username to an array. At the end, you log the usernames, but alas, the array is empty.

```js
const userIds = [1, 2, 3];
const usernames = [];

userIds.forEach(async (userId) => {
  const user = await fetchUserById(userId);
  usernames.push(user.username);
});

// this prints an empty array, no usernames here 🙁
console.log(usernames);
```

So what's going on here? How does this code *actually* run? And how is that different from what you'd expect?

No better way to explain this than an animation of the JavaScript runtime as it executes the program line by line.

<video loop controls src="https://maximorlov.com/videos/async-await-foreach-execution.mp4" aria-label="Animation showing the program's execution. All network requests are kicked off simultaneously and sent to background tasks until completion. Then the usernames are logged before any network request finishes. After some time, the requests finish and the usernames are pushed to the array."></video>

The first thing to notice is that the `fetchUserById` requests are kicked off concurrently inside the forEach method. They're sent to background tasks until completion (WebAPIs in the browser, C++ thread pool in Node.js).

After all requests have started, the program then continues and logs the `usernames`  array, which of course is empty at this point because nothing has been pushed yet.

Aha!

You see, **functional JavaScript methods like forEach are unaware of promises**. Their [callback functions are fired off synchronously](https://maximorlov.com/synchronous-vs-asynchronous-callbacks/) *without* waiting for promises between each iteration.

Then after some time, the requests finish and the array is populated with the usernames. After which the program exits and the usernames are left behind in the dark. 👀

> Note: The order in which the requests finish is random and will rarely be the same order in which they were sent out. This is another reason not to use async/await inside forEach to populate an array — the arrangement of the results will differ from the original array.

Seeing the runtime "jump up" from the end towards the middle is confusing because one of the first things we're taught in programming is that code executes from top to bottom.

So how do you run some code only *after* all asynchronous tasks have completed?

## `Promise.all` and `.map`, a match made in heaven
The solution is to map each element of an array to a promise, and pass the resulting array of promises to `Promise.all`. We have to use Promise.all because the await keyword only works on a single promise.

Promise.all is like an aggregator. Given an array of promises, it returns a *single* promise that fulfills after *all* promises have fulfilled. The returned promise resolves to an array with the results of the input promises.

![A visual representation of how Promise.all works. A cone that takes in an array of promises and unifies them to a single promise that resolves with the results of input promises.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3uo6iua1hirdgj0fx2vr.png)

To fix our example, we'll map the user IDs to an array of promises where each promise resolves with the username. We'll then pass the promises to Promise.all, await it and log the result.

```js
const userIds = [1, 2, 3];

// Map each userId to a promise that eventually fulfills
// with the username
const promises = userIds.map(async (userId) => {
  const user = await fetchUserById(userId);
  return user.username;
});

// Wait for all promises to fulfill first, then log
// the usernames
const usernames = await Promise.all(promises);
console.log(usernames);
```

I've assigned the promises to a separate variable before passing them to Promise.all so it's easier to understand what's happening. In practice, you'll often see the map function written directly inside Promise.all.

That's it! You've learned how to run multiple asynchronous tasks and wait until they've completed before executing the remaining code. 👏

Next time you see async/await inside forEach it should raise an alarm bell because the code probably doesn't work as expected. ⚠️

## Master Asynchronous JavaScript 🚀

Learn how to write modern and easy-to-read asynchronous code with a **FREE 5-day email course**.

Through visual graphics you will learn how to decompose async code into individual parts and put them back together using a modern async/await approach. Moreover, with **30+ real-world exercises** you'll transform knowledge into a practical skill that will make you a better developer.

![Refactoring Callbacks, a FREE 5-day email course. 30+ real-world exercises, a visual guide and 5 days, 5 lessons.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ic1d6os14bjflg46fuyw.png)

👉 [**Get Lesson 1 now**](https://maximorlov.com/refactoring-callbacks-course/)