2019-11-19

Async Foreach

javascript, til, devjournal

banner

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