Skip to main content

Promise.all, promise.race, promise.allsettled, promise.any

Promise.all, Promise.race, Promise.allSettled, and Promise.any are four static methods on the Promise constructor, each coordinating a group of promises with a different strategy for what counts as "done."

Theory

TL;DR

  • Promise.all waits for every promise to succeed; the first rejection cancels everything
  • Promise.race returns whatever settles first, fulfilled or rejected
  • Promise.allSettled always resolves with the outcome of every promise, no matter what
  • Promise.any returns the first success; only rejects if every input fails, via AggregateError
  • Decision rule: need all results? all. Need fastest? race. Need full picture? allSettled. Need first success? any

Quick example

javascript
const p1 = Promise.resolve("fast"); const p2 = new Promise(r => setTimeout(() => r("medium"), 100)); const p3 = Promise.reject("boom"); Promise.all([p1, p2, p3]).catch(e => console.log(e)); // "boom" Promise.race([p1, p2, p3]).then(console.log); // "fast" Promise.allSettled([p1, p2, p3]).then(console.log); // [{status:"fulfilled",value:"fast"},{status:"fulfilled",value:"medium"},{status:"rejected",reason:"boom"}] Promise.any([p3, p1, p2]).then(console.log); // "fast"

p1 is already resolved. p3 rejects immediately. Promise.all bails on p3. Promise.race never reaches p3 because p1 wins first. Promise.allSettled reports all three. Promise.any skips the rejection and returns the first success.

Key difference

all and race short-circuit: all stops at the first rejection, race stops at the first settlement of any kind. allSettled and any keep going regardless. allSettled collects everything; any stops the moment it finds one success. That boundary is where most bugs appear in production async code.

When to use

  • All results are required to proceed: Promise.all (parallel API calls, config loading)
  • Timeout pattern: Promise.race paired with a delay promise that rejects after N ms
  • Partial failures are acceptable: Promise.allSettled (dashboards, batch saves, logging)
  • First available wins: Promise.any (CDN failover, trying multiple servers)
  • Combined: wrap Promise.any inside Promise.race to add a global timeout on top of failover logic

Comparison table

MethodShort-circuits?When it settlesOn successOn failureAdded in
Promise.allYes, first rejectionAll fulfill OR one rejectsArray of values (ordered)First rejection reasonES2015
Promise.raceYes, first settlementAny promise settlesFirst settled valueFirst settled reasonES2015
Promise.allSettledNoAll promises settleArray of {status, value/reason}Never rejectsES2020
Promise.anyYes, first fulfillmentFirst fulfills OR all rejectFirst fulfilled valueAggregateError with all reasonsES2021

How the engine handles this

V8 implements all four as native C++ functions inside PromiseConstructor. Promise.all iterates the input, stores results by index, and triggers rejection the moment any promise rejects without waiting for the rest. Promise.allSettled uses the same counter logic but increments it for both fulfilled and rejected outcomes, so it never short-circuits. Promise.race and Promise.any set a winner flag on the first qualifying settlement. Non-promise values get wrapped in Promise.resolve() automatically, which is why Promise.race([1, slowPromise]) resolves with 1 immediately.

Common mistakes

Expecting Promise.all to collect all errors

javascript
// Wrong: only the first rejection comes through Promise.all([failA(), failB()]).catch(e => { console.log(e); // One error. The other is gone. }); // Fix: use allSettled and filter const results = await Promise.allSettled([failA(), failB()]); const errors = results .filter(r => r.status === "rejected") .map(r => r.reason);

Using Promise.race without a timeout creates hangs

javascript
// Wrong: if neither promise ever settles, this hangs forever Promise.race([slowPromise, anotherSlowPromise]).then(handle); // Fix: always pair race with a timeout promise const withTimeout = (p, ms) => Promise.race([ p, new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), ms) ) ]);

Not handling AggregateError from Promise.any

javascript
// Wrong: e.message is generic, individual reasons are lost Promise.any([fail1, fail2]).catch(e => console.log(e.message)); // Fix: check e.errors for individual reasons Promise.any([fail1, fail2]).catch(e => { if (e instanceof AggregateError) { console.log(e.errors); // array of all rejection reasons } });

Passing non-promise values and losing intent

javascript
Promise.race([1, slowPromise]).then(console.log); // 1, immediately

This works because 1 wraps to Promise.resolve(1). But it signals nothing about intent. Keep all inputs as actual promises.

Mutating allSettled results in place

javascript
// Confusing: mutates result objects directly results.forEach(r => { r.value = r.value?.toUpperCase(); }); // Clear: map to new objects const updated = results.map(r => r.status === "fulfilled" ? { ...r, value: r.value.toUpperCase() } : r );

Real-world usage

  • React Query uses Promise.all internally for useQueries to batch parallel requests
  • Next.js getStaticProps runs data fetchers in parallel via Promise.all
  • SWR calls Promise.allSettled when running mutate on multiple cache keys
  • Express middleware chains use Promise.race for per-route timeouts on external calls
  • Node.js dns.promises batch lookups rely on Promise.all

Follow-up questions

Q: What happens when you pass an empty array to each method?
A: Promise.all([]) and Promise.allSettled([]) resolve immediately with []. Promise.race([]) never settles. Promise.any([]) rejects immediately with an AggregateError.

Q: Promise.race([p1, p2]) where p1 rejects first. What happens to p2?
A: race rejects immediately with p1's reason. p2 keeps running in the background but its result is ignored. The chain has already moved on.

Q: Why does Promise.any throw AggregateError instead of the last rejection reason?
A: Because when all inputs fail, there is no single "last" that carries more meaning than the others. AggregateError collects every reason in e.errors, so nothing is lost. Access individual reasons via e.errors[0], e.errors[1], and so on.

Q: How would you polyfill Promise.any for environments without ES2021?
A: Invert the logic of Promise.all. Track rejections in an array and a fulfilled counter. Resolve on first fulfillment. If all reject, throw new AggregateError(rejectionsArray, "All promises were rejected").

Q: What are the memory implications of allSettled vs all when processing thousands of promises?
A: allSettled holds all result objects in memory until every promise settles. all can release references earlier on rejection. At scale this difference matters; chunk large batches regardless of which method you use.

Examples

Parallel data fetching with Promise.all

javascript
async function fetchUserDashboard(userId) { // All three requests go out at the same time const [user, posts, comments] = await Promise.all([ fetch(`/api/users/${userId}`).then(r => r.json()), fetch(`/api/posts?userId=${userId}`).then(r => r.json()), fetch(`/api/comments?userId=${userId}`).then(r => r.json()) ]); return { user, posts, comments }; }

If any of the three endpoints fail, the whole function throws. That is exactly right here: showing a profile with missing pieces would be worse than showing an error page.

Timeout pattern with Promise.race

javascript
const withTimeout = (promise, ms) => Promise.race([ promise, new Promise((_, reject) => setTimeout(() => reject(new Error(`Timed out after ${ms}ms`)), ms) ) ]); app.get("/data", async (req, res) => { try { const data = await withTimeout(fetchFromDatabase(), 3000); res.json(data); } catch (err) { res.status(503).json({ error: err.message }); } });

I add this wrapper to every Express route that calls an external service. Without it, one slow dependency can hold a request open for minutes.

CDN failover with Promise.any

javascript
async function loadAsset(assetName) { const cdnUrls = [ `https://cdn1.example.com/${assetName}`, `https://cdn2.example.com/${assetName}`, `https://cdn3.example.com/${assetName}` ]; try { return await Promise.any( cdnUrls.map(url => fetch(url).then(r => { if (!r.ok) throw new Error(`${url}: ${r.status}`); return url; }) ) ); } catch (err) { // err.errors contains each individual failure reason throw new Error("All CDN endpoints failed"); } }

Graceful degradation with Promise.allSettled

javascript
async function loadDashboard() { const [userResult, statsResult, feedResult] = await Promise.allSettled([ fetchUser(), // Required fetchStats(), // Optional fetchActivityFeed() // Optional ]); if (userResult.status === "rejected") { throw new Error("Cannot render dashboard without user data"); } return { user: userResult.value, stats: statsResult.status === "fulfilled" ? statsResult.value : null, feed: feedResult.status === "fulfilled" ? feedResult.value : [] }; }

Stats and feed failing is fine. User data failing is not. allSettled hands that decision back to the caller instead of making it for you.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?