Promise Combinators
The promise combinators are four static methods on Promise that take a collection of promises and produce a single promise describing how they finish together: Promise.all, Promise.allSettled, Promise.race and Promise.any. Each answers a different question — all must succeed, tell me how each one ended, the first to finish, the first to succeed — and picking the right one usually replaces a pile of bookkeeping.
Promise.all — succeed together or fail fast
Promise.all(iterable) waits for every promise to fulfil and resolves with an array of their values in input order. If any promise rejects, the returned promise rejects immediately with that reason — the others keep running but their results are ignored.
const users = await Promise.all([
fetch("/api/user/1").then((r) => r.json()),
fetch("/api/user/2").then((r) => r.json()),
fetch("/api/user/3").then((r) => r.json()),
]);
console.log(users.length); // 3, in the same order as the inputUse Promise.all when every result is required and a single failure should abort the batch — for example, loading three files that together form one page.
Promise.allSettled — wait for all outcomes
Promise.allSettled also waits for every promise, but it never rejects. The result is an array of result descriptors — one per input — that tell you, for each, whether it fulfilled and with what, or rejected and with what reason.
const results = await Promise.allSettled([
fetch("/api/a"),
fetch("/api/b"),
fetch("/api/c"),
]);
for (const r of results) {
if (r.status === "fulfilled") console.log("ok", r.value.status);
else console.log("failed", r.reason.message);
}Reach for allSettled when each task is independent and you want to report partial success — bulk imports, dashboards that show "3 of 5 sources loaded", retry batches.
Promise.race — first to settle wins
Promise.race resolves or rejects as soon as any input settles, copying its value or reason. The other promises continue running but their outcomes are discarded.
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error("timeout")), ms),
);
return Promise.race([promise, timeout]);
}
try {
const data = await withTimeout(fetch("/slow"), 2000);
console.log("got data");
} catch (err) {
console.log(err.message); // "timeout" if the fetch is too slow
}Timeouts are the canonical use. Less obvious uses: racing the same query against multiple replicas, or against a cached value and a live fetch.
Promise.any — first success wins
Promise.any is the opposite of Promise.all: it resolves with the value of the first promise that fulfils and only rejects if every input rejects. The combined rejection is an AggregateError whose .errors array lists each failure.
try {
const fastest = await Promise.any([
fetch("https://mirror-1.example.com/file"),
fetch("https://mirror-2.example.com/file"),
fetch("https://mirror-3.example.com/file"),
]);
console.log("served from", fastest.url);
} catch (err) {
console.log(err.errors); // every mirror failed
}Use any when you have several equivalent sources and only need one to succeed — mirrors, CDN fallbacks, multi-region reads.
Side-by-side comparison
Promise.all— all fulfil, fail fast. Result: array of values in input order.Promise.allSettled— all settle, never rejects. Result: array of{status, value | reason}descriptors.Promise.race— first to settle (fulfil or reject). Result or rejection: that one.Promise.any— first to fulfil. Rejection:AggregateErroronly if every input rejected.
Mixing values and promises
All four combinators accept any iterable — and any non-promise value is treated as an already-resolved promise. This lets you mix sync defaults with async lookups without special-casing.
const [config, user] = await Promise.all([
defaultConfig, // a plain object
fetch("/me").then((r) => r.json()), // a promise
]);Concurrency limits — what combinators do not give you
Promise.all starts every task at once. With hundreds of URLs you usually want a concurrency limit. A tiny helper:
async function mapLimit(items, limit, worker) {
const results = new Array(items.length);
let next = 0;
async function run() {
while (next < items.length) {
const i = next++;
results[i] = await worker(items[i], i);
}
}
await Promise.all(Array.from({ length: limit }, run));
return results;
}
const pages = await mapLimit(urls, 5, (url) => fetch(url).then((r) => r.text()));