Promise Race Allsettled And Any

Promise.all isn't the only combinator. Promise.race settles on whichever promise finishes first, win or lose, allSettled never short circuits, and any ignores rejections entirely.

July 12, 20265 min read21 / 22

The last post ended on a tease: Promise.all isn't the only combinator built on top of Promises, just the one worth reaching for first. The other three solve problems Promise.all genuinely can't.

Promise.race: First to Settle Wins

Promise.race takes an array of Promises, same shape as Promise.all, but the Promise it returns settles the moment the first one of them settles, fulfilled or rejected, whichever happens first.

JavaScript · Live Editor
Loading editor...

Hit Run a few times. The winner changes, sometimes Madrid, sometimes Oslo, sometimes Santiago, whichever request's response actually lands first on that particular run. Promise.race hands back a single result, not an array of three, and that result is whatever the fastest Promise settled to. Run it again on a slower connection and a different country wins, since the race depends entirely on real network timing, not on the order the URLs were written in.

A rejection can win the race too. If the fastest Promise to settle happens to be a rejected one, Promise.race rejects with that same reason, even while the other two are still pending and would have succeeded. That's the "race" part: whichever one crosses the line first decides the outcome, win or lose.

The Real Use Case: Racing Against a Timeout

Where Promise.race actually earns its keep is protecting against a request that takes too long. A slow connection can leave a fetch hanging well past the point it's still useful, and nothing about fetch itself gives up after N seconds. A second Promise that rejects on a timer fixes that:

JavaScript
function timeout(seconds) { return new Promise((_, reject) => { setTimeout(() => reject(new Error('Request took too long')), seconds * 1000); }); }

This is the same setTimeout-wrapping shape as wait() from post 14, except it only ever rejects, it never resolves at all. Racing a real request against it means whichever one settles first decides what happens:

JavaScript · Live Editor
Loading editor...

Hit Run. At 0.1 seconds the timeout almost always wins, 💥 Request took too long prints, since a real network round-trip rarely finishes that fast. Push the number up toward 2 or 3 and the real request usually wins instead. Neither promise gets explicitly cancelled, the slower one just finishes its work in the background with no one left listening to its result, Promise.race only decides which outcome the caller actually sees. In a real app the timeout would sit closer to 5 or 10 seconds, just enough to catch a genuinely stuck connection without cutting off a normal slow one.

Promise.allSettled: Never Short-Circuits

Promise.all short-circuits the moment any one Promise rejects, throwing away every other result even if the rest would have succeeded. Promise.allSettled, added in ES2020, is the version that refuses to do that.

JavaScript · Live Editor
Loading editor...

Hit Run. All three results come back, each one wrapped in an object with a status of either 'fulfilled' (with a value) or 'rejected' (with a reason), never a rejection that wipes out the whole batch. Run the same three Promises through Promise.all instead and only the error survives, the two successes never even get logged, since Promise.all gives up entirely at the first rejection. Promise.allSettled is the right call whenever partial results still matter, like showing a user three widgets on a dashboard where one failing shouldn't blank out the other two.

Promise.any: Ignores Rejections Entirely

Promise.any, added in ES2021, behaves like Promise.race with one difference: it skips over rejected Promises completely and only ever settles with the first fulfilled one. A rejection can't win here the way it can with Promise.race.

JavaScript
Promise.any([ Promise.reject(new Error('Error 1')), Promise.resolve('Success 1'), Promise.reject(new Error('Error 2')), ]) .then((first) => console.log(first)) .catch((error) => console.error(error));

Success 1 is what wins here, the two rejections get skipped over entirely rather than being allowed to win the way they could with Promise.race. Promise.any only rejects if every single Promise in the array rejects, in which case it rejects with an AggregateError bundling all the individual failures together, worth checking browser and Node support for before relying on it in older environments.

The four Promise combinators side by side: what each one waits for, the shape of its result, and what happens when a rejection shows up. ExpandThe four Promise combinators side by side: what each one waits for, the shape of its result, and what happens when a rejection shows up.

The Essentials

  1. Promise.race settles as soon as the first Promise in the array settles, fulfilled or rejected, whichever happens first wins.
  2. Racing a real request against a Promise that only rejects on a timer is the practical use for Promise.race, protecting against a request that hangs too long.
  3. Promise.allSettled never short-circuits, it waits for every Promise and returns a status/value-or-reason result for each one, successes and failures both.
  4. Promise.any behaves like Promise.race but skips rejections, settling with the first fulfilled Promise and only rejecting if every single one fails.
  5. Promise.all and Promise.race cover almost everything in practice, allSettled and any are the right tools for their specific, narrower cases.