Any Difference Between Await Promise.All() and Multiple Await

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.

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.

executing multiple await promise.All not catching the error correctly

The binding and setup of the promises was confusing me. Here is a working version of your code.

You probably needed this.index in your bound test() function (not just 'index'). I did not make it a class variable as, due to NodeJS timing, I'm not sure you could have guaranteed the order would match the array. My version just uses the array index.

Also, I found that mapping all the test functions at once started running them all at once, essentially finishing the program after 1.3s. In my code I only started the test function for the chunk I was on, so each chunk takes 1.3s.

Oh, and I didn't see a chunk() function, so I made one.

I hope you find this helpful. Happy coding!

function chunk(array, chunkSize) {
let chunks = []
for (let i = 0; i < array.length; i += chunkSize) {
chunks.push(array.slice(i, i + chunkSize))
}
return chunks
}
class testClass {
async test(num, idx) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (num === 72) {
reject(new Error(`index : ${idx}, num : ${num}`))
} else {
resolve({ failed_skus: [{ id: 'str' }] })
}
}, 1300)
})
}

async executePromises() {
let processedChunks = 1
const productUpdateRequests = [
72, 900, 3, 4, 5, 6, 72, 800, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 72, 100, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3,
4, 5, 6, 7, 8, 9,
]

const requestChunks = chunk(productUpdateRequests, 20)
for (const requestChunk of requestChunks) {
const updateStockResponses = await Promise.all(requestChunk.map((num, idx) => this.test(num, idx)))
.then(() => {
console.log('Finished processing chunk ' + processedChunks)
})
.catch((error) => {
//console.log(error)
console.log('Error processing chunk ' + processedChunks)
})
processedChunks++
}
}
}

let c = new testClass()
c.executePromises()

Will memory usage change between `await Promise.all` and `X = await Promise.all`?

Not assigning the result from await Promise.all() just means that the result will be immediately eligible for garbage collection. The result is still created by Promise.all() as it doesn't know whether you're going to use it or not.

So, whether that creates any difference from your X = await Promise.all(...) purely depends upon how soon X is available for garbage collection. If you use the result immediately and then X goes out of scope and is quickly eligible for garbage collection, then there's probably no difference in the memory usage profile as that value will be created and then garbage collected at a similar time either way.

If, on the other hand, X stays around for a long time (like gets stored in some lasting data structure) then of course, its memory usage will endure for awhile.

Will V8 does some optimizations to reduce memory usage for the first one?

No.



Related Topics



Leave a reply



Submit