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.allwaits for every promise to succeed; the first rejection cancels everythingPromise.racereturns whatever settles first, fulfilled or rejectedPromise.allSettledalways resolves with the outcome of every promise, no matter whatPromise.anyreturns the first success; only rejects if every input fails, viaAggregateError- Decision rule: need all results?
all. Need fastest?race. Need full picture?allSettled. Need first success?any
Quick example
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.racepaired 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.anyinsidePromise.raceto add a global timeout on top of failover logic
Comparison table
| Method | Short-circuits? | When it settles | On success | On failure | Added in |
|---|---|---|---|---|---|
Promise.all | Yes, first rejection | All fulfill OR one rejects | Array of values (ordered) | First rejection reason | ES2015 |
Promise.race | Yes, first settlement | Any promise settles | First settled value | First settled reason | ES2015 |
Promise.allSettled | No | All promises settle | Array of {status, value/reason} | Never rejects | ES2020 |
Promise.any | Yes, first fulfillment | First fulfills OR all reject | First fulfilled value | AggregateError with all reasons | ES2021 |
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
// 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
// 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
// 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
Promise.race([1, slowPromise]).then(console.log); // 1, immediatelyThis 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
// 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.allinternally foruseQueriesto batch parallel requests - Next.js
getStaticPropsruns data fetchers in parallel viaPromise.all - SWR calls
Promise.allSettledwhen runningmutateon multiple cache keys - Express middleware chains use
Promise.racefor per-route timeouts on external calls - Node.js
dns.promisesbatch lookups rely onPromise.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
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
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
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
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 readyA concise answer to help you respond confidently on this topic during an interview.