ForgeFrontend — Prepare, Practice, Crack
Secure checkout
Lifetime access
Instant PDF download
Free updates forever

Prepare · Practice · Crack

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 whenResolves withWaits for
Promise.allAny one rejectsArray of valuesAll, or the first rejection
Promise.allSettledNeverArray of {status, value|reason}All of them, always
Promise.raceFirst settled is a rejectionFirst settled valueThe first to settle, either way
Promise.anyAll of them rejectFirst fulfilled valueThe first success
All four combinators. The first two are what interviewers ask about.

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.

The same three promises through eachjavascript
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.

Order is input order, not completion orderjavascript
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.

The parallelism is the pointjavascript
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 time

Promise.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.

Splitting the resultsjavascript
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 failed

Use 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.

The other work still happensjavascript
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.

Actually cancelling the restjavascript
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

Getting per-item context back from Promise.alljavascript
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 → ₹299

Limiting 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.

A concurrency-limited pool in a dozen linesjavascript
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 moment

Notice 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.

Essential data with all, optional data with allSettledjavascript
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" notice

race and any — completing the family

The follow-up question is nearly always "what about race and any?", and the distinction between them is precise.

race settles first; any succeeds firstjavascript
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.

The timeout patternjavascript
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.

Defensive error reportingjavascript
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.

fetch does not reject on HTTP errorsjavascript
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

  1. Do I need every result, and is one failure fatal? → Promise.all
  2. Is partial success useful, and do I want to report what failed? → Promise.allSettled
  3. Do I want the first result and don't care which? → Promise.any (or Promise.race if a fast failure should also win)
  4. 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
Written by Arun Karthikeyan · Last updated

Full kit

JavaScript Interview Kit · ₹299

Get it →