How to use async functions with Array.forEach in Javascript

How to iterate over the elements asynchronously

2 mins

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

Async forEachjsjsforEachforEache1e1e2e2e3e3Finishedasync321

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

Async forEach, waiting for the resultsjsjsmapmape1e1e2e2e3e3321Finishedasync

With this change, the “Finished async” comes last.

Shameless plug
We write articles like this regularly. Join our mailing list and let's keep in touch.

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

Async forEach, sequential processingjsjsreducereducee1e1e2e2e3e3123Finishedasync

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.

03 March 2020