8

According to JavaScript, Node.js: is Array.forEach asynchronous?, Array.forEach is synchronous. However, for my code below:

function wait5() {
    return new Promise(resolve => 
    setTimeout(resolve, 5000));
}

async function main() {
   console.log("Start");
   [1,2].forEach(async (e) => {
      const d = await wait5().then(()=> console.log("5s later") )
   })
   console.log("This should come last!");
}

main();

The output is:

Start
This should come last!
5s later
5s later

with the two "5s later" coming out in rapid succession.

Why is this the case?

If I use a normal for loop:

async function main() {
   console.log("Start");
   for (let i=0;i<2;i++) {
      const d = await wait5().then(()=> console.log("5s later") )
   }
   console.log("This should come last!");
}

then the result is what I wanted:

Start
5s later
5s later
This should come last!
6
  • 8
    The .forEach() mechanism on its own performs no asynchronous work. The same cannot be said for code that's passed in as the callback, which is free to do anything it wants. Commented Aug 30, 2018 at 17:01
  • 1
    await is the key here. Commented Aug 30, 2018 at 17:02
  • 1
    This has nothing to do with forEach and everything to do with [1,2].forEach(async (e) => where you declared the callback to forEach to be async. Commented Aug 30, 2018 at 17:02
  • And note that nothing pays attention to the Promise returned from each call to the .forEach() callback. Commented Aug 30, 2018 at 17:02
  • This is your second question about asynchronicity in JavaScript. I'm all for that, please keep asking well formed questions. However, is there a core question that might be better asked like "What does asynchronicity actually mean in JS?" or "How does the event loop work?"? I'm not sure how better to help you. Commented Aug 30, 2018 at 17:05

2 Answers 2

10

forEach is synchronous. Your particular callback function, however, is not. So forEach synchronously calls your function, which starts its work, once for each entry in the array. Later, the work that started finishes asynchronously, long after forEach has returned.

The issue is that your callback is async, not that forEach is asynchronous.

In general, when you're using an API like forEach that doesn't do anything with the return value (or doesn't expect a promise as a return value), either:

  1. Don't pass it an async function, or

  2. Ensure that you handle errors within the function itself

Otherwise, you'll get unhandled errors if something goes wrong in the function.

Or of course:

  1. Use a try/catch block within the async function to catch and handle/report errors within the function itself.
Sign up to request clarification or add additional context in comments.

4 Comments

Wouldn't the await call prevent the forEach iteration from continuing until the result is returned?
@OldGeezer - No. await pauses the callback, at which point the callback returns its promise. forEach continues. You can't make a synchronous function asynchronous.
I am still confused. Why does a normal for loop (see updated details) not behave the same?
@OldGeezer - Good question. Because a for loop in an async function is modified by the fact it's an async function; modifying the basic flow-control structure is what async on a function does. forEach isn't modified in that way. (It could have been, when async functions were added, since it's a built-in. But that would have been complicated and even more confusing. So only syntax, not API functions, is modified by async.)
1

It looks like you're declaring an async function inside of a caller that really doesn't care for that sort of thing, forEach. Declaring a function async makes it promise-like, but that promise is only useful if acted on.

If you need a promise-aware forEach, that's something you could implement, though Promise.each in libraries like Bluebird already do that.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.