Search code examples
javascriptnode.jses6-promise

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


This is a basic question, but i couldn't find the answer to it anywhere.

We have two approaches:

// consider someFunction1() and someFunction2() as functions that returns Promises

Approach #1:
return [await someFunction1(), await someFunction2()]

Approach #2:
return await Promise.all([someFunction1(), someFunction2()])

My Team Leader said that both approaches ended up in the same solution (both functions executting in parallel). But, from my knowledge, the first approach would await someFunction1() to resolve and then would execute someFunction2.

So that's the question, is it really the same, or are there any performance improvements on second approach? Proofs are very welcome!


Solution

  • 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.