7

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!
Old Geezer
  • 14,854
  • 31
  • 111
  • 198
  • 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. – Pointy Aug 30 '18 at 17:01
  • 1
    `await` is the key here. – zero298 Aug 30 '18 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`. – gforce301 Aug 30 '18 at 17:02
  • And note that nothing pays attention to the Promise returned from each call to the `.forEach()` callback. – Pointy Aug 30 '18 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. – zero298 Aug 30 '18 at 17:05
  • Possible duplicates of [What is the difference between synchronous and asynchronous programming (in node.js)](https://stackoverflow.com/q/16336367/1260204), [When is JavaScript synchronous?](https://stackoverflow.com/q/2035645/1260204), [How do I return the response from an asynchronous call?](https://stackoverflow.com/q/14220321/1260204) – Igor Aug 30 '18 at 17:14

2 Answers2

9

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.
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Wouldn't the `await` call prevent the forEach iteration from continuing until the result is returned? – Old Geezer Aug 30 '18 at 17:06
  • @OldGeezer - No. `await` pauses the callback, at which point the callback returns its promise. `forEach` continues. You can't make a synchronous function asynchronous. – T.J. Crowder Aug 30 '18 at 17:07
  • 1
    I am still confused. Why does a normal `for` loop (see updated details) not behave the same? – Old Geezer Aug 30 '18 at 17:14
  • 1
    @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`.) – T.J. Crowder Aug 31 '18 at 07:41
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.

tadman
  • 208,517
  • 23
  • 234
  • 262