2

I realized when I do something like this:

for (const entity of someArr) {
  console.log('start now!')
  await doSomeAsycAction()
  console.log('waited X secs!')
}

It prints out:

start now!
waited X secs!
start now!
waited X secs!
...

But when I use map:

arr.map(async entity => {
  console.log('start now!')
  await doSomeAsycAction()
  console.log('waited X secs!')
})

It prints out:

start now!
start now!
start now!
...
waited X secs!
waited X secs!
waited X secs!
...

Can someone explain why this is the case?

David Kim
  • 29
  • 4
  • Does this answer your question? [Execute promises map sequentially](https://stackoverflow.com/questions/57722131/execute-promises-map-sequentially) – Krispies Aug 16 '20 at 14:11
  • 1
    Because `await` only lets the current function wait, and `map` always iterates synchronously (not caring about promise return values). – Bergi Aug 16 '20 at 14:12
  • you may also want to check out https://advancedweb.hu/how-to-use-async-functions-with-array-map-in-javascript/ – rasso Aug 16 '20 at 14:12
  • Second one you are using async which runs the code asynchronousely. check [this](https://www.youtube.com/watch?v=8aGhZQkoFbQ) – Medi Aug 16 '20 at 14:14
  • This might answer -> EventLoop in Javascript and async functions -> https://blog.sessionstack.com/how-javascript-works-event-loop-and-the-rise-of-async-programming-5-ways-to-better-coding-with-2f077c4438b5 – Ajeet Eppakayala Aug 16 '20 at 14:24

2 Answers2

1

The difference between the two flows is that the first one (using for, for..in or for..of) is running the loop iterations sequentially and the other one (using map, filter, reduce, forEach and so on) are running (in case async symbol is used somewhere in the mapping function) concurrently (kind of). In for loops, the next iteration must wait for the previous one to finish. This allows you to do some async operations relevant for the next iteration. In contrast, using the async methods runs each iteration independently, so you can't rely on other iterations in your current iteration. Those kind of functions receive a function as an argument and executes it immediately for every item in the array.

Think of every iteration as an independent promise execution. When running an async function, the await symbol tells that this operation might take a while (i.e I/O, DB calls, network operations...) and let's the code outside of the current executed function keep going (and resume later on, after the async call returns). The map function sees that the current iteration is busy and go on to the next one. Somewhen in the future it would resume and execute console.log('waited X secs!').

You can simulate the same behavior of async executions with for loop this way (would maybe help demonstrating the difference):

for (const entity of someArr) {
  (async () => {
    console.log('start now!')
    await doSomeAsycAction()
    console.log('waited X secs!')
  })()
}

The async-await syntax is working per function scope, and map is defining a new function scope (the function passed as the param to the function) just like the (anonymous) function that gets executed each iteration in my example. Wish it helps to understand.

One important thing to notice is that each iteration of the map doesn't return the mapped value you were expecting for, but a promise that will be resolved with this value. So if you try to rely on one of the mapped array values - you must add an await right before it, otherwise the value type would still be a promise. Take a look at the following example:

let arr = [1];
arr = arr.map(async entity => incrementAsync(entity));
console.log(arr[0]) // would print an unresolved Promise object
console.log(await arr[0]) // would print 2
Shay Yzhakov
  • 975
  • 2
  • 13
  • 31
  • For `await`ing the results of the `map(…)` call, use `Promise.all`. Don't `await` an individual promise from the array only. – Bergi Aug 16 '20 at 17:55
  • @Bergi Any logical reason behind that? What exactly is the diff between await Promise.all(someArr.map()) and await someArr.map() ? – David Kim Aug 16 '20 at 19:08
  • @DavidKim `await someArr.map(…)` simply doesn't work, as `await` does nothing on arrays. My concern was about `await arr[0]`, which [leads to problems with errors from the other promises in the array](https://stackoverflow.com/questions/46889290/waiting-for-more-than-one-concurrent-await-operation). – Bergi Aug 16 '20 at 19:11
  • @DavidKim The difference between the two is that the `await` keyword is irrelevant if the following term isn't a promise. `map` returns an array (in the case above, an array of promises), and `await`ing on an array just returns that array. `Promise.all()` is used to convert an array of promises into a single promise which is resolved upon completion of all of it's promises (thus fails even if just one of them fails). Since `Promise.all()` returns a promise, you can `await` on it, meaning that when all of the promises are being resolved, `Promise.all()` is being resolved. – Shay Yzhakov Aug 18 '20 at 08:48
  • 1
    @ShayYzhakov Sorry, should have been clearer, was talking about awaiting on arrays of async actions, not just any arrays. So I meant like: await someArr.map(async el => {...}) as you did await arr[0] in your example – David Kim Aug 19 '20 at 16:58
  • @DavidKim the type of array of async actions is still an array, and adding `async` before a term of array type does nothing - it only affects promise type. Notice that the types `Array>` and `Promise>` aren't equal - the first isn't `await`able while the second is, and `Promise.all()` is used to convert the first to the second – Shay Yzhakov Aug 20 '20 at 09:15
-2

Basic array prototype loop functions like forEach, map, filter, find etc. don't wait for next iteration.Their basic behavior is to iterate not awaiting. If you want use like waiting function then try to use like below

for (const event of events) {
  if (failure or conditional) {   
    continue;
  } 
}
Rahul Beniwal
  • 639
  • 4
  • 9