Skip to content
Theory · Asynchronous code · lesson 10 of 12

Several promises at once

Picture a cook who has to bake two pies. He can put the first one in the oven, wait for it, and only then put in the second. Or he can put both in at once: then he waits once instead of twice.

Two await lines in a row work the first way. The second timer is set only after the first one has fired:

JavaScript
function wait(ms, value) {
    return new Promise((resolve) => setTimeout(() => {
        console.log("ready", value);
        resolve(value);
    }, ms));
}

async function main() {
    await wait(100, "a");
    await wait(100, "b");
}

main();
// ready a
// ready b

Let's see what happens:

  1. main calls wait(100, "a") and waits at the first await.
  2. After 100 milliseconds, the timer prints ready a, and the promise is fulfilled.
  3. Only now does the call wait(100, "b") start, and another 100 milliseconds later ready b prints.

In total, about 200 milliseconds pass. When the second wait does not depend on the first, this order only wastes time. Both promises can be started at once and awaited together.

Promise.all

Promise.all takes an array of promises and returns one promise. That promise is fulfilled once every promise in the array is fulfilled, and its value is an array of their values. The values come in the order of the input array, not in the order the promises finished:

JavaScript
async function both() {
    const [a, b] = await Promise.all([wait(200, "slow"), wait(100, "fast")]);
    console.log(a, b);
}

both();
// ready fast
// ready slow
// slow fast

Let's go step by step:

  1. Both wait calls run at once, before Promise.all, and both timers start counting.
  2. After 100 milliseconds, the fast timer fires and prints ready fast.
  3. After 200 milliseconds, the slow timer fires and prints ready slow.
  4. Now both promises are fulfilled, and await gives the array ["slow", "fast"].

In the result, "slow" comes first, as in the input array, even though it was ready later. That is why destructuring hands the values to their names without a mix-up. That is how a cook serves dishes in the order they were ordered, not in the order they are ready.

Promise.all itself starts nothing: the timers start counting when wait is called, before it. Promise.all only waits for promises that already exist. If the array is empty, the promise from Promise.all is fulfilled at once with the value [].

Interviewers like to ask this: how do you wait for several independent promises faster than one by one? The answer: create them all at once and wait for them with Promise.all.

The first rejection

If even one promise is rejected, the promise from Promise.all is rejected at once with its reason and does not wait for the rest:

JavaScript
async function failFast() {
    try {
        await Promise.all([wait(100, "a"), Promise.reject(new Error("b failed"))]);
        console.log("all done");
    } catch (e) {
        console.log(e.message);
    }
}

failFast();
// b failed
// ready a

The rejection of Promise.all is thrown at the await line, like the rejection of a single promise, so try…catch catches it. The all done line never prints, and the error appears before timer a fires.

Here the cook analogy stops working. A cook whose first pie burned can take the second one out of the oven too. Promise.all cancels nothing: timer a still prints ready a, but nobody gets its value any more.

You practice asynchronous code in the course's exercises too. You already wrote a retry with await and try…catch in the exercise before this lesson. After its quiz, an exercise on a promise with a time limit waits for you.

Common mistake

The promises are passed as separate arguments instead of one array:

JavaScript
Promise.all(wait(100, "a"), wait(100, "b")); // ✗
TEXT
TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))

Promise.all takes only its first argument and tries to walk through it like an array. The first argument is a promise, not an array, and it cannot be walked through element by element. The word object in the message means that promise. The tail in parentheses is the internal name of the way an object is walked through, and you can skip it. Wrap the promises in square brackets: Promise.all([wait(100, "a"), wait(100, "b")]).

Key points

  • Two await lines in a row wait in turn, while independent promises can be created at once and awaited with Promise.all.
  • The values from Promise.all come in the order of the input array, not in the order they were ready.
  • The very first rejection rejects the promise from Promise.all at once, but the rest of the work is not canceled.
  • Promise.all takes one array, not promises as separate arguments.

Subscribe to take the course

The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.

Sign in another way