Promise Combinators
Reviewed & published by Brayan K
Promise combinators are static methods — Promise.all, allSettled, race, and any — that take an array of promises and combine them into a single promise, letting you coordinate many async operations at once.
Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
They are the tools you reach for whenever you have several async jobs running together — fetching from five APIs, racing a request against a timeout, or collecting results even when some calls fail.
What You'll Learn in This Lesson
- Run async work in parallel with Promise.all
- Collect every outcome with Promise.allSettled
- Add timeouts using Promise.race
- Get the first success with Promise.any
- Understand AggregateError
- Tell parallel apart from sequential timing
📚 Prerequisites: You should be comfortable with promises and async/await. Combinators build directly on those ideas.
🍽️ Real-World Analogy: Imagine ordering food for a table:
- • Promise.all — wait for every dish; if one is cancelled, the whole order is off
- • Promise.allSettled — wait for every dish and note which ones arrived and which failed
- • Promise.race — start eating whatever shows up first, good or bad
- • Promise.any — eat the first dish that actually arrives (ignore cancelled ones)
1️⃣ Promise.all — All or Nothing (Parallel)
Promise.all takes an iterable of promises and returns one promise that resolves to an array of all their values — in the same order you passed them, regardless of which finished first. It is fail-fast: if any promise rejects, the combined promise rejects immediately with that error and the other results are discarded.
Because all the promises are already running, the total time is roughly the time of the slowest one — not the sum. That is the parallel speed-up you came for.
Here is the real-world fan-out pattern with fetch:
⚠️ Heads up: One rejection loses every result. If you need the successful ones even when some fail, reach for Promise.allSettled instead.
// A helper that resolves after `ms` with a label
const after = (ms, label) =>
new Promise((resolve) => setTimeout(() => resolve(label), ms));
async function main() {
const start = Date.now();
// All three start NOW and run together
const results = await Promise.all([
after(100, "user"),
after(200, "posts"),
after(150, "settings"),
]);
const elapsed = Date.now() - start;
console.log("results:", results);
// ✅ Expected output: results: [ 'user', 'posts', 'settings' ]
// Total ≈ the slowest (200ms), NOT 100+200+150 = 450ms
console.log("ran in parallel:", elapsed < 300);
// ✅ Expected output: ran in parallel: true
}
main();2️⃣ Promise.allSettled — Never Gives Up
Promise.allSettled waits for every promise to finish and never rejects. Instead of values, you get an array of result objects: { status: "fulfilled", value } for successes and { status: "rejected", reason } for failures. It is perfect when one slow or broken source should not sink the whole operation.
const ok = (v) => Promise.resolve(v);
const fail = (msg) => Promise.reject(new Error(msg));
async function main() {
const results = await Promise.allSettled([
ok("api-A worked"),
fail("api-B is down"),
ok("api-C worked"),
]);
for (const r of results) {
if (r.status === "fulfilled") {
console.log("✅", r.value);
} else {
console.log("❌", r.reason.message);
}
}
// ✅ Expected output:
// ✅ api-A worked
// ❌ api-B is down
// ✅ api-C worked
const successes = results.filter((r) => r.status === "fulfilled").length;
console.log("successes:", successes); // ✅ Expected output: successes: 2
}
main();🎯 Your Turn #1 — Run Two Promises in Parallel
Use Promise.all to await both promises at once and log the array.
const price = Promise.resolve(19.99);
const stock = Promise.resolve(42);
async function main() {
// 👉 replace ___ with Promise.all so both resolve together
const [p, s] = await ___([price, stock]);
console.log(p, s); // ✅ Expected output: 19.99 42
}
main();3️⃣ Promise.race & Promise.any — First Across the Line
Both settle as soon as a single promise settles, but they disagree about failures. Promise.race takes whichever promise settles first — even if it rejects — which makes it the classic way to bolt a timeout onto slow work. Promise.any ignores rejections and resolves with the first fulfillment; only if every promise rejects does it reject, with an AggregateError whose .errors array holds every reason.
💡 Tip: Use race for timeouts (a fast rejection should win) and any for redundancy (you only need one source to succeed).
const after = (ms, value, shouldFail = false) =>
new Promise((resolve, reject) =>
setTimeout(() => (shouldFail ? reject(new Error(value)) : resolve(value)), ms)
);
async function main() {
// race: the fastest to settle wins, even though it FAILS
try {
await Promise.race([
after(50, "boom", true), // rejects first
after(100, "slow-ok"),
]);
} catch (e) {
console.log("race got:", e.message);
// ✅ Expected output: race got: boom
}
// any: ignores the early failure, takes the first SUCCESS
const winner = await Promise.any([
after(50, "boom", true), // rejects, ignored
after(100, "slow-ok"), // first fulfillment
]);
console.log("any got:", winner);
// ✅ Expected output: any got: slow-ok
// any: if EVERYTHING fails -> AggregateError
try {
await Promise.any([after(20, "x", true), after(40, "y", true)]);
} catch (err) {
console.log("all failed:", err.errors.map((e) => e.message));
// ✅ Expected output: all failed: [ 'x', 'y' ]
}
}
main();🎯 Your Turn #2 — Add a Timeout
Use Promise.race so the slow work loses to the 30ms timeout.
const slow = new Promise((res) => setTimeout(() => res("done"), 200));
const timeout = new Promise((_, rej) =>
setTimeout(() => rej(new Error("too slow")), 30)
);
async function main() {
try {
// 👉 replace ___ with Promise.race so the timeout can win
await ___([slow, timeout]);
} catch (e) {
console.log(e.message); // ✅ Expected output: too slow
}
}
main();🧠 Quick Recall
Predict the output before revealing the answer.
It rejects with "x". Promise.all is fail-fast — the 1 and 3 never reach you.
"rejected" — allSettled never throws; it reports the failure as a result object.
It rejects with an AggregateError because every promise rejected; .errors is ["a", "b"].
Common Errors & Fixes
- 1️⃣ Awaiting in a loop instead of in parallel
- 2️⃣ Using Promise.all when failures are expected
- 3️⃣ Forgetting Promise.any rejects with AggregateError
- 4️⃣ Passing values you never started
🎯 Mini Challenge — Resilient Dashboard Loader
Load three "widgets" in parallel. Two succeed, one fails. Use Promise.allSettled so the dashboard still shows the widgets that loaded and counts the failures.
const widget = (name, ms, fail = false) =>
new Promise((res, rej) =>
setTimeout(() => (fail ? rej(new Error(name + " failed")) : res(name)), ms)
);
async function loadDashboard() {
const results = await Promise.allSettled([
widget("chart", 60),
widget("feed", 40, true), // this one breaks
widget("weather", 80),
]);
const loaded = results
.filter((r) => r.status === "fulfilled")
.map((r) => r.value);
const failures = results.filter((r) => r.status === "rejected").length;
console.log("loaded widgets:", loaded);
// ✅ Expected output: loaded widgets: [ 'chart', 'weather' ]
console.log("failures:", failures);
// ✅ Expected output: failures: 1
}
loadDashboard();Quick Reference — Combinators
| Method | Resolves when | Rejects when |
|---|---|---|
| all | all fulfill | any one rejects |
| allSettled | all settle | never |
| race | first settles (ok) | first settles (fail) |
| any | first fulfills | all reject (AggregateError) |
📚 Keep Learning
- • MDN — Promise.all()
- • MDN — Promise.allSettled()
- • MDN — Promise.any() & AggregateError
🎉 Lesson Complete — Promise Combinators!
- ✅ Promise.all runs work in parallel and is fail-fast
- ✅ Promise.allSettled reports every outcome and never rejects
- ✅ Promise.race takes the first to settle — great for timeouts
- ✅ Promise.any takes the first success, or an AggregateError if all fail
Practice quiz
What does Promise.all resolve to when every promise fulfills?
- The first value only
- An array sorted by finish time
- An array of all values in input order
- A single combined object
Answer: An array of all values in input order. Promise.all resolves to an array of all values, in the same order you passed them in.
What happens to Promise.all if one input promise rejects?
- It rejects immediately and discards other results (fail-fast)
- It ignores the failure
- It waits for the rest then rejects
- It resolves with the successes
Answer: It rejects immediately and discards other results (fail-fast). Promise.all is fail-fast: one rejection rejects the whole thing and the other results are lost.
How does Promise.allSettled differ from Promise.all?
- It rejects faster
- It only takes two promises
- It runs them sequentially
- It never rejects and reports every outcome
Answer: It never rejects and reports every outcome. allSettled waits for every promise and gives an array of { status, value/reason } — it never rejects.
What shape does each Promise.allSettled result object have for a success?
- { ok: true, data }
- { status: 'fulfilled', value }
- { resolved: value }
- Just the value
Answer: { status: 'fulfilled', value }. Fulfilled entries are { status: 'fulfilled', value }; failures are { status: 'rejected', reason }.
Which combinator is the classic choice for adding a timeout to slow work?
- Promise.race
- Promise.all
- Promise.allSettled
- Promise.any
Answer: Promise.race. Promise.race settles with the first promise to settle (even a rejection), so a timeout rejection can win.
How does Promise.any treat rejections?
- It rejects on the first rejection
- It collects them into an array of values
- It ignores rejections and resolves with the first fulfillment
- It retries them
Answer: It ignores rejections and resolves with the first fulfillment. Promise.any resolves with the first fulfillment, ignoring rejections unless they all reject.
What does Promise.any reject with when every input promise rejects?
- The last error
- An AggregateError holding all the reasons
- undefined
- A TypeError
Answer: An AggregateError holding all the reasons. If all reject, Promise.any rejects with an AggregateError whose .errors array holds every reason.
Do the combinators start your promises running?
- Yes, they trigger the work
- Only Promise.all does
- Only with await
- No — promises run when created; the combinators only wait
Answer: No — promises run when created; the combinators only wait. Promises start the moment they are created; combinators merely coordinate when to wait on them.
What is the result of await Promise.all([1, Promise.reject('x'), 3])?
- [1, 'x', 3]
- It rejects with 'x'
- [1, 3]
- It resolves with undefined
Answer: It rejects with 'x'. Promise.all is fail-fast, so the rejection 'x' rejects the whole call and 1 and 3 are discarded.
Which mistake turns parallel work into accidental sequential work?
- Using Promise.all(urls.map(fetch))
- Passing already-running promises
- Awaiting each fetch inside a for loop
- Using allSettled
Answer: Awaiting each fetch inside a for loop. Awaiting one fetch before starting the next serializes them; map them into Promise.all to overlap.
Continue this course
- Previous: Checkpoint: Modern JavaScript
- Next: Async Iterators & for-await-of — Stream and paginate async data with async generators
- Quick reference: JavaScript cheat sheet
Frequently asked questions
What is the difference between Promise.all and Promise.allSettled?
Promise.all is fail-fast: the moment any input promise rejects, the whole thing rejects with that error and you lose the other results. Promise.allSettled never rejects — it waits for every promise to finish and gives you an array describing each outcome ({ status: 'fulfilled', value } or { status: 'rejected', reason }). Use all when every task must succeed; use allSettled when you want partial results.
When should I use Promise.race versus Promise.any?
Both settle as soon as ONE promise settles, but they differ on what counts. Promise.race settles with the first promise to settle either way — so a fast rejection wins. Promise.any ignores rejections and resolves with the first fulfillment, only rejecting (with an AggregateError) if every promise rejects. Use race for timeouts; use any for 'give me the first source that works'.
Do the combinators run my promises in parallel?
The combinators do not start anything — your promises are already running the moment you create them. The combinators simply wait. So if you create all your promises up front and then pass the array to Promise.all, the work overlaps (parallel). If you await each one in a loop before creating the next, you have made them sequential by accident.
What is an AggregateError?
AggregateError is a special error type that wraps several errors at once. Promise.any throws one when all of its input promises reject, exposing every individual reason on its .errors array. It is the only built-in combinator that can produce one, and it lets you inspect why every source failed instead of seeing just a single message.