Promise.allSettled()

ES2020+

Returns a promise that resolves after all of the given promises have either fulfilled or rejected, with an array of objects describing each outcome.

Syntax

Promise.allSettled(iterable)

Parameters

iterable Iterable

An iterable of promises

Return Value

Promise

A Promise that resolves with an array of result objects

Examples

JavaScript
Promise.allSettled([
  Promise.resolve('성공'),
  Promise.reject('실패'),
  Promise.resolve('또 성공')
]).then(results => {
  results.forEach(r => console.log(r.status));
});
Output:
// 'fulfilled' 'rejected' 'fulfilled'

📌 When to Use

Use Promise.allSettled() when you need the outcome of every operation, not just a collective pass or fail. It never rejects: each input is reported as { status: 'fulfilled', value } or { status: 'rejected', reason }, so one failure cannot cost you the results that succeeded. That contract fits batch jobs — sending notifications to a list of recipients, uploading a set of files, syncing records to several services — where the job should run to completion and then report exactly which items need retrying. It is equally right for independent UI panels: a stocks widget dying should not blank the weather widget next to it. Compare the failure semantics with Promise.all() carefully: all() is fail-fast and discards sibling results on the first rejection, which is what you want when the results are only useful together; allSettled() waits for everything and makes you inspect each outcome, which is what you want when results are useful individually. Prefer it too for shutdown-style cleanup — closing connections, flushing logs — where you want to wait for completion but a failure must not throw. If you find yourself reading only the fulfilled values and ignoring the reasons, reconsider: silently discarding failures is a bug farm.

⚠️ Common Mistakes

Treating it like Promise.all() and wrapping it in try/catch expecting a rejection. allSettled() virtually never rejects — the catch block is dead code, and the real failures sit inside the result objects where nothing is looking at them.

Reading result.value without checking result.status first. Rejected entries have no value property (it is undefined) and fulfilled entries have no reason, so unchecked access produces undefined values that flow silently into later logic and corrupt aggregates.

Ignoring the rejected entries entirely. Because nothing throws, it is easy to ship code that processes the successes and never logs the failures — operations then degrade silently in production for weeks. Every allSettled() call site should do something explicit with the rejected list, even if it is only a warning log.

Forgetting that it always waits for the slowest input. There is no early exit on failure: if one promise hangs for thirty seconds, your batch takes thirty seconds even when everything else failed instantly. Combine slow or unreliable inputs with timeouts or AbortController deadlines.

Using it where the group truly is all-or-nothing. If later code needs every value to proceed, allSettled() just postpones the error to a less obvious place; Promise.all() would fail fast at the right moment with a clearer signal. Also note it is an ES2020 feature — older targets need a polyfill.

✅ Best Practices

Partition results immediately into successes and failures — filter on r.status === 'fulfilled' and map to r.value, and collect the reasons from the rest — so the remainder of the function works with two clean arrays instead of tagged unions.

Handle the two outcome shapes explicitly: log or queue retries for the rejected reasons while processing the fulfilled values. In TypeScript the discriminated union on status narrows automatically inside each branch, giving you type-safe access to value and reason.

Use the guaranteed ordering: results[i] always corresponds to inputs[i], so you can zip outcomes back onto the source items to build precise per-item error reports or targeted retry queues.

Build retry loops from the reasons: collect the inputs that failed, apply exponential backoff, and re-run only those through another allSettled() pass. Batch endpoints and queue consumers get incremental, idempotent recovery almost for free with this pattern.

⚡ Performance Notes

Runtime cost is essentially Promise.all() plus one small wrapper object per input: every promise gets a reaction record, and settlements are delivered through the microtask queue as usual. The meaningful performance difference is temporal rather than allocative: allSettled() has no short-circuit path, so completion time is always the maximum of all input durations, whereas all() can reject long before slow inputs finish. If failures are common and slow — network timeouts are the classic case — a batch can spend most of its life waiting to learn about failures it could have reported earlier, so bound each input with its own timeout when latency matters. Memory-wise, all values and reasons are held until the final settlement, meaning very large batches with large payloads should be chunked. The result objects themselves are cheap, but allocating millions of them in analytics-style processing is measurable; process such workloads in windows instead of one giant call.

🌍 Real World Example

Batch Email Sender

A bulk email job that must report per-recipient outcomes rather than aborting on the first bounce. Every send is attempted, then the results are partitioned by status into a delivery report: successful addresses on one side, failed addresses with their error messages on the other. The caller can persist the report, alert when the failure rate crosses a threshold, and feed just the failed recipients into a retry queue — none of which is possible with Promise.all(), which would discard all outcome detail at the first rejection.

async function sendBulkEmails(recipients, emailContent) {
  const sendPromises = recipients.map(email =>
    sendEmail(email, emailContent)
      .then(() => ({ email, sent: true }))
  );

  const results = await Promise.allSettled(sendPromises);

  const report = {
    total: recipients.length,
    successful: results.filter(r => r.status === 'fulfilled').map(r => r.value.email),
    failed: results.filter(r => r.status === 'rejected').map((r, i) => ({
      email: recipients[i],
      error: r.reason.message
    }))
  };

  console.log(`Sent: ${report.successful.length}, Failed: ${report.failed.length}`);
  return report;
}

Related Methods