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 ofPromise.allwaiting 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)),]);Async Result:
The first question is: is
Promise.allwaiting 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 withPromise.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'));async console outputWe'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.allto create a promise. It should contain a two-element array with the fulfilled values frompromise1andpromise2.const promise1 = Promise.resolve('first');const promise2 = new Promise(resolve => {setTimeout(() => resolve('second'), 500);});const combined = Promise.all([promise1, promise2]);combined;Async Result:
- Goal:
{fulfilled: ['first', 'second']}- Yours:
{fulfilled: ['first', 'second']}
If one of the promises rejects,
Promise.allimmediately rejects with that promise's rejection reason.>
Promise.all([new Promise((resolve, reject) => resolve(1)),new Promise((resolve, reject) => reject(new Error('oh no'))),]);Async Result:
Promise.allwaits 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.allwill wait for that last promise, so it will see the rejection after a delay. The overallPromise.allpromise 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))]);Async Result:
{rejected: 'Error: oh no'}What if multiple promises reject? In that case, the first one to reject "wins".
Promise.allwill 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)),]);Async Result:
{rejected: 'Error: the first rejection in time wins'}The entire
Promise.allrejects 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.allwork at all?Suppose that we're making multiple HTTP requests with a browser's
fetchfunction, which returns a promise each time it's called. We callfetchmultiple times, then usePromise.allto 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 anXMLHttpRequestobject, which is an older network request API provided by browsers. We set theXMLHttpRequest'sonloadproperty to be a callback function that calls our promise'sresolve. That's all!Now it's the browser's job to make the HTTP request and call our
onloadcallback when the request finishes. Until then, none of our JavaScript code runs.Eventually, the HTTP request finishes, so the browser calls our
onloadcallback, which callsresolve, resolving the promise. The newly-resolved promise calls anythencallbacks that are waiting for it, like aPromise.allthat might be waiting for thisfetchand several others. Those promises call anythencallbacks 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!