Using async/await with Array.forEach() does not work as expected
June 12, 2021
You probably came across the need to loop over an array while using async/await, and the Array.forEach() method seems like an easy choice. The problem is that “await” in a forEach loop does not seem to work. Here is a simple example:
// function returning a promise:
const returnSth = (x) => {
return new Promise((resolves, rejects) => {
setTimeout(() => {
resolves(x);
}, 1000);
});
};
// example with a forEach
const exampleOne = () => {
const arr = [1, 2, 3, 4];
arr.forEach(async (item) => {
const result = await returnSth(item);
console.log(result);
});
console.log("after");
};
exampleOne();
// output: after 1 2 3 4
undefined
and therefore is not chainable.You could expect such a result: 1 2 3 4 after
but it is not. In this case “after” is logged
immediately, and all the numbers after 1 second, long after the exampleOne() function execution
finished. The quick fix is to use a for...of
statement to loop over the array. This way each
iteration waits for the returnSth() function execution to finish. It happens in a sequential
order.
// example with a for...of loop
const exampleTwo = async () => {
const arr = [1, 2, 3, 4];
for (const item of arr) {
const result = await returnSth(item);
console.log(result);
}
console.log("after");
};
exampleTwo();
// output: 1 2 3 4 after
The console output is that each number is displayed one by one, after a second and another second and another, and then after the last number, “after” is displayed immediately. The reason why this is such a different behaviour is that Array.forEach() expects a synchronous function, and it simply does not wait for promises. It just calls the function, not waiting for the result. You can read more about the Array.forEach() method in the MDN docs.
If you don’t want to run each iteration sequentially, and you need to run them in parallel,
there is another solution. Each of the async callback function calls returns a promise that we don’t
want to throw away, so we can use Array.map() method instead of Array.forEach(). With
Promise.all
you can wait for all the promises to resolve before continuing to the next line of
code.
// example with Promise.all and map
const exampleThree = async () => {
const arr = [1, 2, 3, 4];
await Promise.all(
arr.map(async (item) => {
const result = await returnSth(item);
console.log(result);
})
);
console.log("after");
};
exampleThree();
// output: 1 2 3 4 after
This way you get all the logs after around one second. As you can see there are different solutions to the async/await problem with Array.forEach() method, and you can choose whichever solves your problem best.