Why does async/await in a .forEach not actually await?

If you're trying to loop over a list while using async/await in Node.js (or the browser, for that matter), reaching for the .forEach array function might seem like a natural choice. Let's say you go that route, fire up your tests or your application, and expect the synchronous-reading magic of async/await to do what it says and actually await the promise.

What you get instead is either a result in the incorrect order or even the dreaded ReferenceError: 'x' is not defined if your application relies on this return value somewhere else.

You may not be sure if it's a bug or if this the intended output (but it probably feels more like a bug).

Note: in this post we'll be talking about this from the point of view of sequential looping. Meaning we want to be able to loop over an array/list in sequential order.

Fortunately the fix for this is quite simple. Before we go into that, let's take a look at a simplified example of this below:

const example = async () => {
  const nums = [1,2,3];
  nums.forEach(async num => {
   const result = await returnNum(num);
   console.log(result);
  });
  console.log('after forEach');
}

const returnNum = x => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(x);
    }, 500);
  });
}

example().then(() =>{
  console.log('done');
})

The result: after forEach done 1 2 3

What you'd expect: 1 2 3 after foreach done

In order to fix this, switch to a for...of iterator. This will do the trick:

const example = async () => {
  const nums = [1,2,3];
  for (const num of nums) {
   const result = await returnNum(num);
   console.log(result);
  }
  console.log('after forEach');
}

const returnNum = x => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(x);
    }, 500);
  });
}

example().then(() => {
  console.log('done');
})

But why did switching to a for...of work while the .forEach did not? .forEach expects a synchronous function and won't do anything with the return value. It just calls the function and on to the next. for...of will actually await on the result of the execution of the function.

Now your test is back to green and your app is working as expected!

Subscribe for more Node.js content delivered directly to your inbox

No spam ever. Unsubscribe any time.