Using Promise allSettled for Reliable JS APIs

Promise.allSettled waits for every promise to finish and reports each result. Learn how to handle partial failures, batch API calls safely, and process mixed success and error responses.

7 min read

Promise.allSettled takes an array of promises and returns a single promise that resolves after every input promise has settled. Unlike Promise.all, it never short-circuits on rejection. It waits for all of them, then gives you a result object for each one.

Each result object has a status field. A fulfilled result carries a value field with the resolved value, and a rejected result carries a reason field with the error instead.

javascriptjavascript
const promises = [
  fetch("/users/1").then((r) => r.json()),
  fetch("/users/2").then((r) => r.json()),
  fetch("/users/broken").then((r) => r.json()),
];
 
const results = await Promise.allSettled(promises);

Looping over the results shows both outcomes side by side, since each entry carries its own status instead of the whole batch failing together.

javascriptjavascript
results.forEach((result, i) => {
  if (result.status === "fulfilled") {
    console.log(`User ${i}:`, result.value.name);
  } else {
    console.error(`User ${i} failed:`, result.reason.message);
  }
});

Even though the third fetch fails, the first two results are still available. The function does not throw. Partial failure becomes data you can inspect.

Promise.allSettled syntax and return value

The method takes one iterable of promises and always resolves, never rejects, with an array describing what happened to each one.

javascriptjavascript
const results = await Promise.allSettled(iterable);

It takes one parameter, an iterable of promises (usually an array), and returns a promise that resolves to an array of settlement objects, one per input promise.

Each entry in that array takes one of two shapes depending on whether the matching promise resolved or rejected.

javascriptjavascript
// Fulfilled result
{ status: "fulfilled", value: "resolved value" }
 
// Rejected result
{ status: "rejected", reason: "error object" }

The returned array preserves the original order. The first result object corresponds to the first promise in the input, regardless of which promise settled first.

How allSettled compares to other combinators

JavaScript has four promise combinators, and each one answers a different question about a batch of promises. The table below summarizes when each settles and what it hands back.

CombinatorSettles whenResult on partial failure
Promise.allAll resolve, or one rejectsRejects immediately on first rejection
Promise.allSettledEvery promise has settledResolves with a status object per promise
Promise.raceThe first promise settlesSettles with that one result, win or fail
Promise.anyThe first promise resolvesIgnores rejections until one succeeds or all fail

The diagram below traces the same logic as a decision flow, starting from a plain array of promises.

Promise combinators: when each one settles

allSettled is the only one of the four that never rejects on its own. Every other combinator has some rejection path, while allSettled turns every outcome, success or failure, into data in the result array.

Filtering results by status

The most common pattern after allSettled is separating successes from failures into two plain arrays.

javascriptjavascript
async function fetchUsers(ids) {
  const promises = ids.map((id) =>
    fetch(`/users/${id}`)
      .then((r) => (r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`))))
  );
 
  const results = await Promise.allSettled(promises);
  const succeeded = results.filter((r) => r.status === "fulfilled").map((r) => r.value);
  const failed = results.filter((r) => r.status === "rejected");
 
  return { succeeded, failed };
}

Filtering twice like this, once for each status, keeps the logic readable even though it walks the results array twice.

javascriptjavascript
console.log(`Loaded 3 users, checking which ones failed...`);
const { succeeded, failed } = await fetchUsers([1, 2, 3]);
console.log(`${succeeded.length} succeeded, ${failed.length} failed`);

This pattern is invaluable for dashboards, data pipelines, and any scenario where you want to show partial results instead of an all-or-nothing error screen.

Batch API calls that tolerate failures

A real-world example is loading data from multiple independent endpoints for a single page, where no one section should be able to block the others.

javascriptjavascript
async function loadDashboardData() {
  const results = await Promise.allSettled([
    fetch("/api/profile").then((r) => r.json()),
    fetch("/api/notifications").then((r) => r.json()),
    fetch("/api/analytics").then((r) => r.json()),
    fetch("/api/recommendations").then((r) => r.json()),
  ]);
 
  return buildDashboard(results);
}

The small helper below turns the raw results array into the shape the page actually needs, reading each result's status once instead of repeating that check inline for every field.

javascriptjavascript
function buildDashboard(results) {
  return {
    profile: getValue(results[0], null),
    notifications: getValue(results[1], []),
    analytics: getValue(results[2], {}),
    recommendations: getValue(results[3], []),
  };
}

The status check itself lives in one tiny function, reused for every field above instead of being repeated four times with only the fallback value changing between calls.

javascriptjavascript
function getValue(result, fallback) {
  return result.status === "fulfilled" ? result.value : fallback;
}

Each section of the dashboard gets its data or a sensible fallback. One slow or broken endpoint does not block the other three. The user sees a partially populated page instead of a spinner or an error.

Processing mixed results with reduce

For more complex aggregation, use reduce to build a summary from the results array in a single pass.

javascriptjavascript
function addToSummary(acc, result) {
  if (result.status === "fulfilled") {
    acc.succeeded += 1;
    acc.values.push(result.value);
  } else {
    acc.failed += 1;
    acc.errors.push(result.reason.message);
  }
  return acc;
}

This callback runs once per result and adds it to either the succeeded or the failed side of the accumulator, so reduce only needs a single pass over the array to build the whole summary.

javascriptjavascript
function summarizeResults(results) {
  return results.reduce(addToSummary, { succeeded: 0, failed: 0, values: [], errors: [] });
}

The starting accumulator gives every batch a count of zero and empty arrays to fill in, so summarizeResults always returns the same shape even when the input array is empty.

javascriptjavascript
async function batchProcess(items, asyncFn) {
  const results = await Promise.allSettled(items.map(asyncFn));
  return summarizeResults(results);
}

Calling the function produces one summary object with counts and categorized results, useful for processing uploads, sending emails, or any batch job that needs a report at the end.

javascriptjavascript
const summary = await batchProcess([1, 2, 3], fetchUserById);
console.log(`Batch complete: ${summary.succeeded} ok, ${summary.failed} failed`);

When to use allSettled vs Promise.all

Promise.allPromise.allSettled
Behavior on rejectionRejects immediatelyReports rejection, continues
ReturnsArray of resolved valuesArray of status objects
Use whenEvery promise must succeedPartial success is acceptable
Error handlingSingle catch for allInspect each result individually

Choose all when the promises depend on each other and one failure means the whole operation is invalid, such as saving a user record and their profile in a transaction where either both succeed or both should roll back.

Choose allSettled when the promises are independent and you want to collect as many results as possible, such as loading widgets on a dashboard or fetching data from several third-party APIs.

javascriptjavascript
// Promise.all: all-or-nothing
async function createOrder(userId, items) {
  const user = await fetchUser(userId);
  const order = await createOrderRecord(user.id, items);
  const confirmation = await sendConfirmation(user.email, order.id);
  return confirmation; // Any step failing means the whole thing fails
}

The order-creation steps above depend on each other in sequence, so any failure should stop the whole operation. Refreshing independent feeds is the opposite case, where losing one feed should not block the rest.

javascriptjavascript
// Promise.allSettled: collect everything you can
async function refreshAllFeeds(feedUrls) {
  const results = await Promise.allSettled(
    feedUrls.map((url) => fetch(url).then((r) => r.json()))
  );
 
  return results.filter((r) => r.status === "fulfilled").map((r) => r.value);
}

Common allSettled mistakes

Destructuring without checking status. A fulfilled result has a value field, a rejected result has a reason field instead. Accessing value on a rejected result returns undefined, so always check status first.

javascriptjavascript
// Mistake: accessing .value without checking status
results.forEach((r) => console.log(r.value.name)); // r.value is undefined for rejections

Checking the status before reading either field avoids that undefined access entirely, since the if block only ever runs for results that actually carry a value.

javascriptjavascript
// Correct: check status before accessing fields
results.forEach((r) => {
  if (r.status === "fulfilled") {
    console.log(r.value.name);
  }
});

Expecting order to match completion time. The results array preserves input order, not completion order. If the promise at index 2 finishes first, its result still appears at index 2.

Using allSettled when you need all-or-nothing. If every promise must succeed for the operation to be valid, use Promise.all instead. Using allSettled and then checking whether anything failed is just Promise.all with extra steps.

Not handling the rejection reasons. A rejection reason can be an Error object, a string, a number, or undefined, since it is simply whatever value the promise was rejected with.

javascriptjavascript
function getErrorMessage(result) {
  if (result.status === "fulfilled") return null;
  return result.reason instanceof Error ? result.reason.message : String(result.reason);
}

For more on the other promise combinators, see How to Use Promise Race in JS: Complete Guide and Promise All in JavaScript: Complete Guide.

Rune AI

Rune AI

Key Insights

  • Promise.allSettled waits for every promise to finish and never short-circuits on rejection.
  • Each result has a status field: 'fulfilled' with a value, or 'rejected' with a reason.
  • Use it for batch operations where partial failure is acceptable, like multi-source data fetching.
  • Filter results by status to separate successes from failures for reporting or recovery.
  • Promise.all is better when every promise must succeed for the operation to be valid.
RunePowered by Rune AI

Frequently Asked Questions

What is the difference between Promise.all and Promise.allSettled?

Promise.all rejects immediately if any promise rejects. Promise.allSettled waits for every promise to settle and returns an array of result objects, one per promise, each with a status of fulfilled or rejected.

What does a settled result object look like?

A fulfilled result is { status: 'fulfilled', value: ... }. A rejected result is { status: 'rejected', reason: ... }. Every result has a status field so you can check what happened.

Is Promise.allSettled supported in all browsers?

Yes. Promise.allSettled has been available in all modern browsers since 2020. Node.js added support in version 12.9.

Conclusion

Promise.allSettled is the safe choice when you need to run multiple promises and process every outcome, successful or not. It turns partial failure from a crash into data you can inspect, filter, and recover from.