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.
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.
Promise.all([getStat('inventory'), getStat('shipping'), getStat('payments')]).then(
(results) => {
console.log(results);
}
);[ '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."
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:
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));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.
Keep reading