Promise.all vs Promise.allSettled
Short answer
Promise.all rejects as soon as any promise rejects, so you get either every value or one error. Promise.allSettled never rejects — it waits for all of them and gives you an array of {status, value} or {status, reason} objects. Use all when you need everything; allSettled when partial success is acceptable.
| Rejects when | Resolves with | Waits for | |
|---|---|---|---|
| Promise.all | Any one rejects | Array of values | All, or the first rejection |
| Promise.allSettled | Never | Array of {status, value|reason} | All of them, always |
| Promise.race | First settled is a rejection | First settled value | The first to settle, either way |
| Promise.any | All of them reject | First fulfilled value | The first success |
The one-line difference
Promise.all is all-or-nothing: one failure and the whole thing rejects, discarding the results that did succeed. Promise.allSettled is a reporter: it waits for every promise to finish, then hands you the outcome of each one, successes and failures together, and never rejects itself.
1const tasks = [
2 Promise.resolve("a"),
3 Promise.reject(new Error("boom")),
4 Promise.resolve("c"),
5];
6
7await Promise.all(tasks);
8// → throws Error: boom (the "a" and "c" results are lost)
9
10await Promise.allSettled(tasks);
11// → [
12// { status: "fulfilled", value: "a" },
13// { status: "rejected", reason: Error: boom },
14// { status: "fulfilled", value: "c" },
15// ]Promise.all — fail fast
Promise.all resolves with an array of values in the same order you passed them in — not the order they finished. It rejects the moment any input rejects, with that first rejection reason.
const slow = new Promise(r => setTimeout(() => r("slow"), 100));
const fast = Promise.resolve("fast");
const result = await Promise.all([slow, fast]);
// → ["slow", "fast"] (input order preserved, waits for slow)Use it when the results are only meaningful together. Loading a dashboard that needs the user, their permissions and their workspace before it can render anything is the classic case — if permissions fail, there is no partial dashboard worth showing.
1// Sequential — 300ms total
2const user = await getUser();
3const posts = await getPosts();
4const tags = await getTags();
5
6// Parallel — 100ms total (as slow as the slowest)
7const [user, posts, tags] = await Promise.all([getUser(), getPosts(), getTags()]);
8// → same three results, one third of the wall-clock timePromise.allSettled — never rejects
Promise.allSettled always fulfils. Every element of its result is an object describing what happened, so you never lose the successes because of one failure. It was added in ES2020, considerably later than Promise.all, which is why a lot of older code works around its absence.
1const results = await Promise.allSettled(urls.map(u => fetch(u)));
2
3const ok = results.filter(r => r.status === "fulfilled").map(r => r.value);
4const failed = results.filter(r => r.status === "rejected").map(r => r.reason);
5
6console.log(`${ok.length} succeeded, ${failed.length} failed`);
7// → 8 succeeded, 2 failedUse it whenever partial success is genuinely useful: uploading ten files and reporting which two failed, hitting several analytics endpoints where one being down should not break the page, or any batch job that should carry on regardless.
The gotcha: Promise.all does not cancel anything
This is the detail that separates people who have read about promises from people who have debugged them. When Promise.all rejects, the other promises keep running. Promises are not cancellable — Promise.all simply stops caring about them.
1const a = new Promise((_, rej) => setTimeout(() => rej(new Error("fail")), 10));
2const b = new Promise(res => setTimeout(() => { console.log("b ran!"); res("b"); }, 50));
3
4try { await Promise.all([a, b]); } catch (e) { console.log("caught:", e.message); }
5// → caught: fail (at 10ms)
6// → b ran! (at 50ms — it never stopped)So if those promises have side effects — writing to a database, charging a card — a rejected Promise.all does not undo or prevent them. To actually stop in-flight work you need AbortController and requests that support a signal.
1const ctrl = new AbortController();
2try {
3 await Promise.all(urls.map(u => fetch(u, { signal: ctrl.signal })));
4} catch (e) {
5 ctrl.abort(); // → the still-pending fetches are aborted
6 throw e;
7}Handling errors well with each
1// Problem: Promise.all's rejection tells you WHAT failed, not WHICH ONE.
2// Attaching a catch per item keeps the mapping and prevents unhandled rejections.
3const results = await Promise.all(
4 ids.map(id =>
5 fetchItem(id)
6 .then(value => ({ id, ok: true, value }))
7 .catch(error => ({ id, ok: false, error }))
8 )
9);
10// → never rejects, and every result knows its id
11// (at which point allSettled is usually the cleaner choice)That pattern is worth knowing because it also shows the relationship between the two: Promise.all over promises that can never reject behaves exactly like Promise.allSettled. allSettled just gives you it for free, with a standard result shape.
This is 1 of 80+ questions in the JavaScript Interview Kit
Get every question with detailed answers, follow-ups and real code — plus coding challenges and a last-minute revision sheet. One-time payment, instant access.
⚡ Get the JavaScript Interview Kit → ₹299Limiting concurrency
A follow-up that comes up in senior interviews: what if you have 500 URLs? Passing all of them to Promise.all fires 500 requests at once, which will exhaust the browser's connection pool, trip server rate limits, or in Node exhaust file descriptors. Neither combinator throttles anything — they only wait.
1async function pool(items, limit, worker) {
2 const results = [];
3 const running = new Set();
4
5 for (const item of items) {
6 const p = worker(item).finally(() => running.delete(p));
7 running.add(p);
8 results.push(p);
9 if (running.size >= limit) await Promise.race(running);
10 }
11 return Promise.allSettled(results);
12}
13
14await pool(urls, 5, u => fetch(u));
15// → at most 5 requests in flight at any momentNotice this uses three combinators together: race to wait for a slot to open, allSettled to collect every outcome at the end, and the array of promises that map produced. Being able to sketch this shows you understand them as building blocks rather than isolated API calls.
A realistic example
The choice usually isn't one or the other across a whole app — it's per group of requests, based on whether that group is essential.
1async function loadDashboard(userId) {
2 // Essential — if any of these fail there's no dashboard to show.
3 const [user, workspace] = await Promise.all([
4 getUser(userId),
5 getWorkspace(userId),
6 ]);
7
8 // Optional — a broken widget shouldn't take the page down.
9 const extras = await Promise.allSettled([
10 getNotifications(userId),
11 getRecentActivity(userId),
12 getRecommendations(userId),
13 ]);
14
15 return {
16 user,
17 workspace,
18 widgets: extras
19 .filter(r => r.status === "fulfilled")
20 .map(r => r.value),
21 degraded: extras.some(r => r.status === "rejected"),
22 };
23}
24// → renders fully, or renders with a "some widgets unavailable" noticerace and any — completing the family
The follow-up question is nearly always "what about race and any?", and the distinction between them is precise.
1const slowOk = new Promise(r => setTimeout(() => r("ok"), 100));
2const fastBad = new Promise((_, rej) => setTimeout(() => rej(new Error("nope")), 10));
3
4await Promise.race([slowOk, fastBad]);
5// → throws Error: nope (fastBad settled first — rejection counts)
6
7await Promise.any([slowOk, fastBad]);
8// → "ok" (ignores rejections, waits for a success)Promise.race is how you build a timeout: race the real work against a promise that rejects after n milliseconds. Promise.any rejects only if every input rejects, and when it does it throws an AggregateError whose .errors array holds all of them.
1function withTimeout(promise, ms) {
2 const timeout = new Promise((_, rej) =>
3 setTimeout(() => rej(new Error(`Timed out after ${ms}ms`)), ms)
4 );
5 return Promise.race([promise, timeout]);
6}
7
8await withTimeout(fetch("/api/slow"), 3000);
9// → resolves normally, or throws "Timed out after 3000ms"What ends up in reason
A detail that bites people in production: reason is whatever was thrown, and JavaScript lets you throw anything. It is usually an Error, but a rejected promise can carry a string, an object, or undefined — so code that assumes reason.message exists will itself throw while handling the error.
1const results = await Promise.allSettled(tasks);
2
3for (const r of results) {
4 if (r.status === "rejected") {
5 const message = r.reason instanceof Error
6 ? r.reason.message
7 : String(r.reason); // handles strings, objects, undefined
8 console.error("task failed:", message);
9 }
10}
11// → task failed: Network request failed
12// → task failed: undefined (rather than crashing the handler)The other surprise is fetch specifically: a 404 or 500 response does not reject. fetch only rejects on network-level failures, so an array of fetch calls can come back entirely "fulfilled" while half of them are error pages. You have to check response.ok yourself.
1const results = await Promise.allSettled(
2 urls.map(async (u) => {
3 const res = await fetch(u);
4 if (!res.ok) throw new Error(`${res.status} ${u}`); // ← you must do this
5 return res.json();
6 })
7);
8// → without the throw, a 500 arrives as { status: "fulfilled", value: <error page> }Choosing between them
- Do I need every result, and is one failure fatal? → Promise.all
- Is partial success useful, and do I want to report what failed? → Promise.allSettled
- Do I want the first result and don't care which? → Promise.any (or Promise.race if a fast failure should also win)
- Do I need a timeout? → Promise.race against a rejecting timer
What happens if you pass an empty array?
Promise.all([]) and Promise.allSettled([]) both resolve immediately with an empty array. Promise.race([]) is the odd one out — it returns a promise that stays pending forever, since nothing can ever settle it. Promise.any([]) rejects immediately with an AggregateError.
Can you pass non-promise values?
Yes. Every combinator wraps non-promise values with Promise.resolve(), so Promise.all([1, fetchThing(), "x"]) is valid and resolves with [1, <result>, "x"]. That makes it safe to build the array from a mix of cached and pending values.
How do you type allSettled results in TypeScript?
TypeScript models a result as PromiseSettledResult<T>, a discriminated union of PromiseFulfilledResult<T> and PromiseRejectedResult. Narrow on r.status === "fulfilled" and TypeScript makes r.value available; in the other branch you get r.reason, typed as any because JavaScript can throw anything.
Does Promise.all run the promises in parallel?
It doesn't start anything — promises are eager, so they began executing the moment you created them. Promise.all only waits. This is why building the array first and awaiting it once is faster than awaiting each in turn: the work overlaps because you created them all up front.
How would you implement Promise.all yourself?
Return a new Promise, keep a results array and a counter, and iterate the inputs wrapping each with Promise.resolve so non-promises work too. On each fulfilment write the value at its original index — that's what preserves input order — and increment the counter; resolve when the counter reaches the input length. Reject immediately on any rejection, and remember the empty-array case must resolve straight away.
Is Promise.allSettled slower than Promise.all?
For the successful case they're equivalent. The difference appears on failure: Promise.all can settle as soon as the first rejection arrives, while allSettled always waits for the slowest promise. So allSettled is never faster and is sometimes slower — you're trading latency for complete information, which is usually the right trade when partial results matter.
Can you use these with async iterators or streams?
Not directly — all four combinators take a synchronous iterable of promises, so they need the full list up front. For data that arrives over time, use for await...of over an async iterable, which processes each value as it appears rather than waiting for a complete collection. Node's stream helpers and the Web Streams API cover the same ground for larger pipelines.
Frequently asked questions
- What is the difference between Promise.all and Promise.allSettled?
- Promise.all rejects as soon as any input promise rejects, discarding the results that already succeeded. Promise.allSettled waits for every promise regardless of outcome and always fulfils, giving you an array describing each result as either {status: "fulfilled", value} or {status: "rejected", reason}.
- When should I use Promise.allSettled instead of Promise.all?
- Whenever partial success is useful and you want to know what failed — uploading several files and reporting which ones didn't make it, or calling multiple independent endpoints where one being down shouldn't break the page. Use Promise.all when the results are only meaningful together.
- Does Promise.all cancel the other promises when one fails?
- No. Promises are not cancellable; Promise.all simply stops waiting. The other promises keep running to completion, including any side effects they cause. To genuinely stop in-flight work you need AbortController with requests that accept a signal.
- What does Promise.allSettled return?
- An array in input order, one entry per promise. Fulfilled entries are {status: "fulfilled", value}, rejected entries are {status: "rejected", reason}. It never rejects, so the array always has an entry for every input.
- Can Promise.allSettled reject?
- No — it always fulfils, which is exactly what makes it useful. That also means wrapping it in try/catch achieves nothing; you must inspect the results array yourself for entries with status "rejected".
- What is the difference between Promise.all, race, any and allSettled?
- all waits for everything and rejects on the first failure. allSettled waits for everything and never rejects. race settles with whichever promise settles first, whether that's a success or a failure. any waits for the first success and only rejects — with an AggregateError — if every promise rejects.
- Does Promise.all preserve order?
- Yes. The resolved array is in the order you passed the promises in, never the order they completed. Promise.allSettled behaves the same way.
- How do I type Promise.allSettled in TypeScript?
- The result element type is PromiseSettledResult<T>, a discriminated union. Narrow with r.status === "fulfilled" to access r.value; in the rejected branch you get r.reason, typed as any because JavaScript permits throwing any value.
- What is the difference between axios.all and Promise.all?
- There isn't one of substance — axios.all is a thin wrapper around Promise.all that predates widespread native support. It's deprecated, and you should use Promise.all directly with axios calls.
- What happens with Promise.all on an empty array?
- It resolves immediately with an empty array, as does Promise.allSettled([]). Promise.race([]) is the exception: it returns a promise that never settles, because there is nothing to settle it.
- Is Promise.allSettled supported everywhere?
- It's ES2020 and supported in all modern browsers and Node 12.9+. Only very old environments need a polyfill, which is why plenty of legacy code hand-rolls the same behaviour by attaching a .catch() to each promise before calling Promise.all.
- Why does Promise.all cause an unhandled rejection warning?
- Once Promise.all has rejected on the first failure, a later rejection from another input has no handler attached. Node reports that as an unhandled rejection. If multiple failures are expected, attach a .catch() to each promise individually, or use Promise.allSettled.
This is 1 of 80+ questions in the JavaScript Interview Kit
Get every question with detailed answers, follow-ups and real code — plus coding challenges and a last-minute revision sheet. One-time payment, instant access.
⚡ Get the JavaScript Interview Kit → ₹299