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
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.
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