The problem with using async/await in a forEach
loop in JavaScript is the fact that
you can’t wait for the promise resolution. Let’s see how you can fix it.
Why async/await doesn’t work with forEach
in JavaScript
So, you’re calling an async
function in the forEach
loop, and you noticed that
the next iteration of the loop wouldn’t await
for the async
function to return the result.
Imagine you have a function getUsers
that returns the list of users.
This list, however, doesn’t contain the user details, which you’d like to print to the console.
So, you write the forEach
and try to get the details for every user with the function
getDetails
.
const users = await getUsers();
users.forEach(user => {
const details = user.getDetails();
console.log(details);
})
The first go is a mess as you just got 3 pending promises.
Promise { <pending> }
Promise { <pending> }
Promise { <pending> }
Of course, you think to yourself. user.getDetails
is an async
function and all async
functions return Promises.
So you go ahead and try to fix the issue by adding the await
keyword.
const users = await getUsers();
users.forEach(user => {
const details = await user.getDetails();
console.log(details);
})
You run the code, but there’s another error.
SyntaxError: await is only valid in async function
Of course, you realise, the function passed into forEach
should be marked as async
when you use await
in it.
One more fix.
const users = await getUsers();
users.forEach(async (user) => {
const details = await user.getDetails();
console.log(details);
})
Another run, but to no avail. You stare at an empty console, having no idea why there’s no output.
3 viable solutions to the async/await
problem and the forEach
on JS array
You can resolve the issue with async/await
in the forEach
JavaScript loop by rewriting
your code into the plain for
loop.
const users = await getUsers();
for (let i = 0; i < users.length; i++) {
const details = await user.getDetails();
console.log(details);
}
Or, you can achieve the same outcome with the for ... of
JS loop.
const users = await getUsers();
for (const user of users) {
const details = await user.getDetails();
console.log(details);
}
If you don’t like simple code, then you can do a clever trick by using map
instead of forEach
.
const users = await getUsers();
const userDetailsPromises = users.map(user => user.getDetails());
const userDetails = await Promise.all(userDetailsPromises);
console.log(userDetails);
Let’s take a look step-by-step at what’s happening in the example above:
- Nothing new in the first line, we get an array of users and store it in the
users
variable. - Then, we iterate over the array of
users
withmap
, callinggetDetails
on every user. - As we need to
await
for the resolution of every Promise in theuserDetailsPromises
array, we pass it toPromise.all
and add theawait
keyword in front of it. - Eventually, we log the array of
userDetails
to the console.
This code can be simplified further by combining the second, and the third lines.
const userDetails = await Promise.all(users.map(user => user.getDetails()));