For Await of VS Promise.All

for await of VS Promise.all

Yes, they absolutely are different. for await is supposed to be used with asynchronous iterators, not with arrays of pre-existing promises.

Just to make clear,

for await (const res of items.map(e => somethingAsync(e))) …

works the same as

const promises = items.map(e => somethingAsync(e));
for await (const res of promises) …

or

const promises = [somethingAsync(items[0]), somethingAsync(items[1]), …];
for await (const res of promises) …

The somethingAsync calls are happening immediately, all at once, before anything is awaited. Then, they are awaited one after another, which is definitely a problem if any one of them gets rejected: it will cause an unhandled promise rejection error. Using Promise.all is the only viable choice to deal with the array of promises:

for (const res of await Promise.all(promises)) …

See Waiting for more than one concurrent await operation and Any difference between await Promise.all() and multiple await? for details.

Any difference between await Promise.all() and multiple await?

Note:

This answer just covers the timing differences between await in series and Promise.all. Be sure to read @mikep's comprehensive answer that also covers the more important differences in error handling.


For the purposes of this answer I will be using some example methods:

  • res(ms) is a function that takes an integer of milliseconds and returns a promise that resolves after that many milliseconds.
  • rej(ms) is a function that takes an integer of milliseconds and returns a promise that rejects after that many milliseconds.

Calling res starts the timer. Using Promise.all to wait for a handful of delays will resolve after all the delays have finished, but remember they execute at the same time:

Example #1
const data = await Promise.all([res(3000), res(2000), res(1000)])
// ^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^
// delay 1 delay 2 delay 3
//
// ms ------1---------2---------3
// =============================O delay 1
// ===================O delay 2
// =========O delay 3
//
// =============================O Promise.all

async function example() {
const start = Date.now()
let i = 0
function res(n) {
const id = ++i
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
console.log(`res #${id} called after ${n} milliseconds`, Date.now() - start)
}, n)
})
}

const data = await Promise.all([res(3000), res(2000), res(1000)])
console.log(`Promise.all finished`, Date.now() - start)
}

example()

Promise.all vs [await x, await y] - Is it really the same?

No, you should not accept that:

return [await someFunction1(), await someFunction2()];

Is the same as:

return await Promise.all([someFunction1(), someFunction2()]);

I should also note that await in the above return await is not needed. Check out this blog post to learn more.

They are different!



The first approach (sequential)

Let's determine the difference by inspecting how each of the two alternatives works.

[await someFunction1(), await someFunction2()];

Here, in an async context, we create an array literal. Note that someFunction1 is called (a function which probably returns a new promise each time it gets called).

So, when you call someFunction1, a new promise is returned, which then "locks" the async context because the preceding await.

In a nutshell, the await someFunction1() "blocks" the array initialization until the returned promise gets settled (by getting resolved or rejected).

The same process is repeated to someFunction2.

Note that, in this first approach, the two promises are awaited in sequence. There is, therefore, no similarity with the approach that uses Promise.all. Let's see why.

The second approach (non-sequential)

Promise.all([someFunction1(), someFunction2()])

When you apply Promise.all, it expects an iterable of promises. It waits for all the promises you give to resolve before returns a new array of resolved values, but don't wait each promise resolve until waiting another one. In essence, it awaits all the promises at the same time, so it is a kind of "non-sequential". As JavaScript is single-threaded, you cannot tell this "parallel", but is very similar in the behavior point of view.

So, when you pass this array:

[someFunction1(), someFunction2()]

You are actually passing an array of promises (which are returned from the functions). Something like:

[Promise<...>, Promise<...>]

Note that the promises are being created outside Promise.all.

So you are, in fact, passing an array of promises to Promise.all. When both of them gets resolved, the Promise.all returns the array of resolved values. I won't explain in all details how Promise.all works, for that, I suggest you checking out the documentation.

You can replicate this "non-sequential" approach by creating the promises before using the await. Like so:

const promise1 = someFunction1();
const promise2 = someFunction2();
return [await promise1, await promise2];

While promise1 is being waited, promise2 is already running (as it was created before the first await), so the behavior is similar to Promise.all's.

Should I use await inside Promise.all?

Should I use await inside Promise.all?

No (at least not the way you're doing it). Promise.all accepts and expects an array of Promises. Once they all resolve, or if one rejects, the Promise.all will resolve or reject. If you use await, you'll be passing an array of plain non-Promise values to Promise.all, which isn't the logic you want. If you use await, you'll also be waiting for the Promises to resolve serially, rather than in parallel, defeating the entire point of Promise.all. For example:

await Promise.all([
await checkUsernameExists(username),
await checkEmailExists(email),
])...

If checkUsernameExists takes 0.5 seconds to resolve, and checkEmailExists also takes 0.5 seconds to resolve, it will take at least 1 second for the Promise.all to resolve, because the Promises are being resolved via the await checkUsernameExistss, not by the Promise.all itself.

You should definitely do

await Promise.all([
checkUsernameExists(username),
checkEmailExists(email),
])

Async functions return Promises - to the Promise.all, someFnThatReturnsAPromise() is the same as somePromise. So there's absolutely nothing wrong with invoking the function and putting the resulting Promise in the array to pass to Promise.all.

Using Async/Await vs Promise.all with for-loops

My guess is that you resolve too fast, so the looping dominates the execution time, since method2 has two loops: one to push to the array and one for promise.all to complete while method1 only has one loop, so your feeling "almost a factor of two" is actually theoretically correct.

Try faking an actual async operation like new Promise(resolve => setTimeout(resolve, 200)) and you should get what you expected

What is the difference between async/await forEach and Promise.all + map

The O.P.'s question was asking for clarification on another StackOverflow question found here. For further reading, and many other great answers on this general topic, please take a look at the link.

For Googlers who only saw the question title

Don't use async/await with forEach. Either use a for-of loop, or use Promise.all() with array.map().

If you have a general understanding on promises and async/await, the TL;DR on the differences between promise.all() + array.map() and .forEach(), is that it's impossible to await a forEach() itself. Yes, you can run tasks in parallel in .forEach() just like you can with .map(), but you can't wait for all of those parallel tasks to finish, then do something once they've all finished. The whole point of using .map() instead of .forEach() is so you can get a list of promises, collect them with Promise.all(), then await the whole thing. To see what I mean, just put a console.log('Finished') after a forEach(async () => ...), and you'll see the "finished" get logged out before everything has finished running in the .forEach() loop. My advice would be to just not use .forEach() with async logic (and really there's not a reason use ever use .forEach() anymore these days, as I explain further down).

For those who need something a bit more in depth, the rest of this answer will dive into more detail, first giving a small review on promises, then showing an in-depth explanation on how these approaches behave differently and why .forEach() is always the inferior solution when it comes to async/await.

A primer on promises and async/await

For the purposes of this discussion, you just have to remember that a promise is a special object that's promising that some task is going to be completed at some point in the future. You can attach listeners to a promise via .then(), to get notified when the task is completed, and to receive the resolved value.

An async function is simply a function that will always return a promise, no matter what. Even if you do async function doThing() { return 2 }, it's not going to return 2, it's going to return a promise that immediately resolves to the value 2. Note that an asynchronous function will always return a promise immediately, even if it takes a long time for the function to run. This is why it's called a "promise", it's promising that the function will eventually finish running, and if you want to be notified for when the function has finished, you can add an event listener to it, via .then() or await.

await is special syntax that lets you pause execution of an async function until a promise resolves. await will only effect the function it's directly inside. Behind the scenes, await is simply adding a special event listener to the promise's .then(), so it can know when the promise resolves and what value it resolves with.

async fn1() {
async fn2() {
await myPromise // This pauses execution of fn2(), not fn1()!
}
...
}

async function fn1() {
function fn2() {
await myPromise // An error, because fn2() is not async.
}
...
}

If you can get a good grasp of these principles, then you should be able to understand the next sections.

for-of

A for-of loop lets you execute your asynchronous tasks in serial, one after another. For example:

const delays = [1000, 1400, 1200];

// A function that will return a
// promise that resolves after the specified
// amount of time.
const wait = ms => new Promise(resolve => setTimeout(resolve, ms))

async function main() {
console.log('start')
for (const delay of delays) {
await wait(delay)
console.log('Finished waiting for the delay ' + delay)
}
console.log('finish')
}

main()


Related Topics



Leave a reply



Submit