Execute Program

JavaScript Concurrency: Promise.all

Welcome to the Promise.all lesson!

This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!

  • We've seen Promise.all, but only briefly. It takes an array of promises, collects all of their values into an array, and returns a promise fulfilled with that array. Here's an example of Promise.all waiting on four promises, each fulfilling in a different way or at a different time.

  • >
    Promise.all([
    Promise.resolve(1),
    new Promise(resolve => resolve(2)),
    Promise.resolve(2).then(n => n + 1),
    new Promise(resolve => setTimeout(() => resolve(4), 500)),
    ]);
    Asynchronous Icon Async Result:
  • The first question is: is Promise.all waiting to run the promises one by one (sequentially)? Or do they all run at the same time (concurrently)? To find out, we create two promises that each take 500 ms, then run both with Promise.all. If the whole process takes about 500 ms, we know that they ran concurrently. If it takes about 1000 ms, we know that the second one only started after the first one.

  • >
    Promise.all([
    new Promise(resolve => setTimeout(resolve, 500)),
    new Promise(resolve => setTimeout(resolve, 500)),
    ])
    .then(() => console.log('done'));
    Asynchronous Icon async console output
  • We've confirmed it: the promises run at the same time. (We'll explore the implications of that a bit more in a moment.)

  • Here's a code problem:

    Use Promise.all to create a promise. It should contain a two-element array with the fulfilled values from promise1 and promise2.

    const promise1 = Promise.resolve('first');
    const promise2 = new Promise(resolve => {
    setTimeout(() => resolve('second'), 500);
    });
    const combined = Promise.all([promise1, promise2]);
    combined;
    Asynchronous Icon Async Result:
    Goal:
    {fulfilled: ['first', 'second']}
    Yours:
    {fulfilled: ['first', 'second']}Pass Icon
  • If one of the promises rejects, Promise.all immediately rejects with that promise's rejection reason.

  • >
    Promise.all([
    new Promise((resolve, reject) => resolve(1)),
    new Promise((resolve, reject) => reject(new Error('oh no'))),
    ]);
    Asynchronous Icon Async Result:
  • Promise.all waits until all promises have fulfilled, or until one of them rejects. What about the unfortunate case where most of the promises fulfill instantly, but one of them rejects after a long delay? Regardless of how long it takes, Promise.all will wait for that last promise, so it will see the rejection after a delay. The overall Promise.all promise will reject with that promise's reason.

  • >
    Promise.all([
    new Promise((resolve, reject) => resolve(1)),
    new Promise((resolve, reject) =>
    setTimeout(() => reject(new Error('oh no')), 1000)
    )
    ]);
    Asynchronous Icon Async Result:
    {rejected: 'Error: oh no'}Pass Icon
  • What if multiple promises reject? In that case, the first one to reject "wins". Promise.all will reject with that promise's reason. "First" here means "the first one in time", not "the one with the lowest array index".

  • >
    Promise.all([
    new Promise((resolve, reject) =>
    setTimeout(
    () => reject(new Error('the first rejection in the array wins')),
    500
    )
    ),
    new Promise((resolve, reject) =>
    setTimeout(
    () => reject(new Error('the first rejection in time wins')),
    1
    )
    ),
    ]);
    Asynchronous Icon Async Result:
    {rejected: 'Error: the first rejection in time wins'}Pass Icon
  • The entire Promise.all rejects as soon as any one of the underlying promises rejects. That makes some sense: if we're waiting on 100 promises and one goes wrong, we usually don't want to wait for the other 99 to finish!

  • Let's take one last look at the idea of promises running "at the same time". JavaScript runtimes only run one piece of code at a time; two different functions can never be actively executing simultaneously. Then how does Promise.all work at all?

  • Suppose that we're making multiple HTTP requests with a browser's fetch function, which returns a promise each time it's called. We call fetch multiple times, then use Promise.all to wait for all of those promises at once. As we saw above, the promises will run concurrently, so we'll only have to wait for the slowest of them, rather than waiting for each to happen one by one.

  • Here's a portion of the implementation of fetch, with many details removed.

  • >
    export function fetch(...) {
    return new Promise(function(resolve) {
    ...

    var xhr = new XMLHttpRequest();

    xhr.onload = function() {
    resolve(new Response(...));
    };

    ...
    }
  • We create a promise with new Promise. We create an XMLHttpRequest object, which is an older network request API provided by browsers. We set the XMLHttpRequest's onload property to be a callback function that calls our promise's resolve. That's all!

  • Now it's the browser's job to make the HTTP request and call our onload callback when the request finishes. Until then, none of our JavaScript code runs.

  • Eventually, the HTTP request finishes, so the browser calls our onload callback, which calls resolve, resolving the promise. The newly-resolved promise calls any then callbacks that are waiting for it, like a Promise.all that might be waiting for this fetch and several others. Those promises call any then callbacks that are waiting for them. And so on, callbacks calling callbacks, until the JavaScript runtime has nothing to do again and the CPU goes idle.

  • This shows us a crucial fact about JavaScript: everything is callbacks! Our JavaScript code is callbacks, promises are callbacks, older asynchronous APIs like Node's file and network libraries are callbacks, the JavaScript libraries that we use are callbacks, and the browser's built-in APIs are callbacks. It's callbacks all the way down!