How to use async functions with Array.forEach in Javascript
How to iterate over the elements asynchronously
In the first article, we've covered how async/await helps with async commands but it offers little
help when it comes to asynchronously processing collections. In this post, we'll look into the forEach
function, which is helpful when you need
to run a piece of code for every element in a collection.
forEach
The forEach
function is similar to the map
, but instead of transforming the values and using the results, it runs the function for each element and
discards the result. Effectively, the important part is the side effects of calling the function.
For example, printing each element to the console, synchronously:
const arr = [1, 2, 3];
arr.forEach((i) => {
console.log(i);
});
// 1
// 2
// 3
console.log("Finished sync");
// Finished sync
As the result is not important, using an async function as the iteratee would work:
const arr = [1, 2, 3];
arr.forEach(async (i) => {
// each element takes a different amount of time to complete
await sleep(10 - i);
console.log(i);
});
console.log("Finished async");
// Finished async
// 3
// 2
// 1
Controlling the timing
Waiting for finish
But, not unsurprisingly, the function is called asynchronously, and the program execution goes past the call. This is an important difference from the sync version,
as, by the time the next line is executed, the synchronous forEach
is already done, while the async version is not.
That's why the "Finished async" log appears before the elements.
To wait for all the function calls to finish before moving on, use a map
with a Promise.all
and discard the results:
const arr = [1, 2, 3];
await Promise.all(arr.map(async (i) => {
await sleep(10 - i);
console.log(i);
}));
// 3
// 2
// 1
console.log("Finished async");
// Finished async
With this change, the "Finished async" comes last.
Sequential processing
But notice that the iteratee functions are called in parallel. To faithfully follow the synchronous forEach
, use
a reduce
with an await memo
first:
const arr = [1, 2, 3];
await arr.reduce(async (memo, i) => {
await memo;
await sleep(10 - i);
console.log(i);
}, undefined);
// 1
// 2
// 3
console.log("Finished async");
// Finished async
This way the elements are processed in-order, one after the other, and the program execution waits for the whole array to finish before moving on.
Conclusion
The async forEach
is easy to use, but whether you should use a forEach
, a map
, or a reduce
depends on the requirements for timing.
If you just want to run the functions no matter when, use a forEach
. If you want to make sure it finishes before moving on, use a map
.
And finally, if you need to run them one by one, use a reduce
.