Promise.all and Promise.race

Promise.all waits for every promise and hands back results in request order. Promise.race waits for whichever settles first, which is also how you time one out.

September 6, 20263 min read17 / 17

Here's the actual idea this post teaches, in one sentence: Promise.all waits for every promise in a list to succeed and hands back their results in request order, while Promise.race only waits for whichever one settles first, success or failure, which is also the standard way to put a timeout on a promise that might never finish.

Waiting for All of Them: A Gate

Every dashboard example so far has printed each stat as soon as it was safe to. Sometimes that's not what you actually want, sometimes you need all three stats before showing anything at all, together, before moving on.

JavaScript
Promise.all([getStat('inventory'), getStat('shipping'), getStat('payments')]).then( (results) => { console.log(results); } );
Plain text
[ 'inventory data ready', 'shipping data ready', 'payments data ready' ]

results comes back in the exact order the promises were listed, not the order they actually finished in. Run this a hundred times with random delays, and the array is always [inventory, shipping, payments], even though any of the three might have technically resolved first behind the scenes.

This pattern has an old name: a gate. Multiple things are happening, you don't know or care what order they'll finish in, but nothing moves forward until every one of them is done. Promise.all is a gate.

One thing worth knowing: if any single promise in the list rejects, Promise.all rejects immediately. It doesn't wait around for the other two. One failure fails the whole gate.

Waiting for Whichever Finishes First: A Race

Promise.race answers a different question: not "did all of these finish," but "which one finished first."

JavaScript
Promise.race([promiseA, promiseB]).then((firstResult) => { console.log(firstResult); });

Whichever promise settles first, resolved or rejected, is the one that decides the outcome. Everything else in the list just gets ignored once that happens.

The Practical Use for a Race: Timing Something Out

Every promise in this series so far has assumed the thing it's waiting on eventually finishes. Real network calls don't always keep that promise.

Promise.race is the standard way to protect against a promise that might just hang forever:

JavaScript
function timeout(ms) { return new Promise((_, reject) => { setTimeout(() => reject(new Error('Timed out')), ms); }); } Promise.race([getStat('inventory'), timeout(500)]) .then((data) => console.log('Got:', data)) .catch((err) => console.error('Failed:', err.message));
Two promises enter the race
The real request, and a timer that rejects after 500ms.
If the real request wins
Its value flows into .then(), exactly as if the timeout promise never existed.
If the timer wins instead
The rejection lands in .catch(), and the real request's result, whenever it eventually shows up, gets ignored.

Run this with a slow, 2-second delay against a 500ms timeout, and you get Failed: Timed out every time, exactly as expected.

You Could Invent More of These, but You Don't Have To

Promise.all and Promise.race aren't the only two abstractions someone could dream up. A version that only cares about the first success (ignoring early failures), or one that waits for every promise to finish regardless of outcome, are both real patterns some libraries add on top of these two.

The instinct worth keeping isn't memorizing every possible variant. It's the same one from working with lists: recognize the shape of the problem, "wait for all of them" or "wait for whichever's first," and reach for the tool built for that shape instead of hand-rolling your own version of Promise.all from scratch.