4

I am having hard time in understanding how for...of differ from .forEach() in case of handling Promises.

Using this snippet:

const allSettled = async arr => {
    const data = []

    for (const promise of arr) {
        try {
            const result = await promise;
            data.push(result);
        } catch (e) {
            data.push(e)
        }
    }

    return data
}

I go over each promise in array, wait for it to settle and push the result to data. It executes sequentially.

If I have this snippet:

const badAllSettled = arr => {
    const data = []

    arr.forEach(async promise => {
        try {
            const result = await promise;
            data.push(result);
        } catch (e) {
            data.push(e)
        }
    })

    return data
}

I get empty array (because forEach does not wait for Promise to settle).

AFAIK for...of works with iterables, hence it might suspend and wait await to return. But how this flow works step-by-step I don't understand.

Thanks!

danhuong
  • 202
  • 1
  • 8
Nikita Shchypyplov
  • 1,090
  • 1
  • 9
  • 18
  • `.forEach()` does not understand Promises. – Pointy Feb 25 '21 at 17:45
  • 2
    `for … of` is control flow, affected by `await` (just like other loops, if statements etc). `forEach` is just a method call, and the `await` only makes the `async` function wait but not `badAllSettled`. – Bergi Feb 25 '21 at 17:48

3 Answers3

3

.forEach() is implemented like this:

Array.prototype.forEach = function forEach(fn) {
  for (let i = 0; i < this.length; i++)
    fn(this[i], i, this);
}

As you can see, when .forEach() is called, it just calls the callback synchronously several times. However, if the callback is an async function, it doesn't get awaited.

By contrast, when an await keyword is found inside an async function, all the code execution withing that function is paused until the promise settles, which means that any control flow (including for ... of loops) is also paused.

The following code behaves like .forEach() because it calls the async function which awaits the promise without awaiting the promise the function returns:

const allSettled = async arr => {
    const data = []

    for (const promise of arr) {
        (async () => {
            try {
                 const result = await promise;
                 data.push(result);
            } catch (e) {
                data.push(e)
            }
        )();
    }

    return data
}
D. Pardal
  • 6,173
  • 1
  • 17
  • 37
2

for...of doesn't wait. The await keyword does the waiting. You have one of those here: const result = await promise;

The internals of forEach don't await the promise returned by the function you pass to forEach(here). (The use of await inside the function that generates that promise is irrelevant).

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
1
for (const item of array) {
  // do something
}

does "something" for each element of the array. If you await something inside, it will await it until proceeding to the rest of the code.

array.forEach(callback)

synchronously calls the callback for each element of the array. If your callback happens to be asynchronous, it will call it and then immediately call the next callback. .forEach doesn't wait for each callback to resolve to something before calling the next callback.


Note that if you want your promises to run in parallel, perhaps for perf gains, you might wanna use Promise.all

// runs every fetch sequentially
// if each of them takes 1s, this will take 3s to complete
const results = []
for (const item of [1,2,3]) {
  results.push(await fetchSomething(item))
}

// runs every fetch in parrallel
// if each of them takes 1s, this will take 1s to complete
const promises = [1,2,3].map(item => fetchSomething(item))
const results = await Promise.all(promises)
Nikita Shchypyplov
  • 1,090
  • 1
  • 9
  • 18
Nino Filiu
  • 16,660
  • 11
  • 54
  • 84
  • Thanks for an explanation! Promise.all does not tolerate rejects, hence I have this "kinda" allSettled polyfill. Also I tried usual for loop and it also works alright. – Nikita Shchypyplov Feb 25 '21 at 18:15