Read an article Exploring the JavaScript forEach Method for Looping Over Arrays by Jack Misteli today.
Jack announced the post on Coding Blocks Slack channel, and mentioned that,
I think most of you won't learn anything here it's pretty beginner stuff, hopefully there will be a nugget of new information.
He was right. I did get "a nugget of new information".
Wasn't aware of async foreach method can work differently that I thought.
Setting await outside Array#foreach doesn't necessarily await/resolve promise inside it.
Reproducing the behavior
An await on Array#foreach would not resolve promise(s) inside it.
In the code snippet below, following output will print all at once after one second, NOT wait 1 second per number.
1const waitFor = ms => new Promise(r => setTimeout(r, ms));2
3[1, 2, 3].forEach(async n => {4 await waitFor(1000);5 console.log(`first number`, n);6});
result after 1 second (1000 milliseconds).
1first number 12first number 23first number 3
Go ahead and click the play ▶ button to run the sample below.
But what if you want to wait for 1 second per each iteration?
Using asyncForEach method
I also googled and foudn this articled, JavaScript: async/await with forEach(), which dived deeper into the particular topic.
The author, Sébastien Chopin, created a wrapper method named asyncForEach, which accepts an array, and a callbac, to operate on the array.
1async function asyncForEach(array, callback) {2 for (let index = 0; index < array.length; index++) {3 await callback(array[index], index, array);4 }5}
It reads like, given an array, await on callback of each array item.
The code below
1async function asyncForEach(array, callback) {2 for (let index = 0; index < array.length; index++) {3 await callback(array[index], index, array);4 }5}6
7const waitFor = ms => new Promise(r => setTimeout(r, ms));8async function main() {9 asyncForEach([1, 2, 3], async n => {10 await waitFor(1000);11 console.log(`main 'asyncForEach' num=${n}`);12 });13}14
15main();
results in following output, where each line is printed after one second.
1main 'asyncForEach' num=12main 'asyncForEach' num=23main 'asyncForEach' num=3
Go ahead and click the play ▶ button to run the sample below.
Using "for await...of".
There is a new syntax in town, for await...of, which became avaiable in ECMAScript2018.
Let's say, you are mapping over an array, which does an async task.
Overly contrived for brevity
1// You can use `fetch` logic here to get users or posts, etc. for `getN`.2const getN = async n => n;3const waitFor = ms => new Promise(r => setTimeout(r, ms));4
5async function main() {6 for await (const n of [1, 2, 3].map(getN)) {7 await waitFor(1000);8 console.log(`main 'for await of' num=${n}`);9 }10}11
12main();
The code above will output each line every second, not at once.
1main 'for await of' num=12main 'for await of' num=23main 'for await of' num=3
The behavior is the same as using asyncForEach wrapper, just imperative.
Bonus
Jack extended Array prototype as
1Array.prototype.asyncForEach = async function (callback) {2 let k = 0;3 while (k < this.length) {4 if (!this[k]) return;5 let element = this[k];6 // This will pause the execution of the code7 await callback(element, k, this);8 k += 1;9 }10};
and my friend, Nicolas Marcora helped me to understand the simplified implementation.
1Array.prototype.asyncForEach = async function (callback) {2 for (const index in this) {3 const element = this[index];4 // This will pause the execution of the code5 await callback(element, index, this);6 }7};
for...in would return an index of the this array, and you can get an element of each, which you can await on.
Image by PublicDomainPictures from Pixabay