Using Promise.allSettled for Reliable JavaScript APIs
Learn how Promise.allSettled enables partial-success patterns in JavaScript. Understand how it differs from Promise.all, handle mixed outcomes gracefully, and build resilient API calls that never fail silently.
Promise.all is great when every operation must succeed. But real applications deal with partial failure constantly — a batch of API calls where some succeed and some fail, enriching data from optional sources, or retrying only the failed subset of a batch. Promise.allSettled is designed precisely for these scenarios. It never rejects and gives you a complete picture of every outcome.
The Core Problem Promise.allSettled Solves
With Promise.all, one failure rejects the entire result:
// Building a dashboard from three data sources
const [users, metrics, featureFlags] = await Promise.all([
fetchUsers(), // Critical
fetchMetrics(), // Critical
fetchFeatureFlags(), // Optional — outage would kill the whole dashboard
]);If fetchFeatureFlags() has a temporary outage, the entire dashboard breaks, even though users and metrics loaded fine. Promise.allSettled lets you continue with what you have.
How Promise.allSettled Works
Promise.allSettled waits for every Promise to settle — fulfilled OR rejected — and resolves with an array of outcome objects:
const results = await Promise.allSettled([
fetchUsers(),
fetchMetrics(),
fetchFeatureFlags(),
]);
// results is always an array with the same length as the input:
// [
// { status: "fulfilled", value: [...users] },
// { status: "fulfilled", value: {...metrics} },
// { status: "rejected", reason: Error("Feature flags service unavailable") },
// ]Each element is either:
{ status: "fulfilled", value: <result> }— the Promise fulfilled{ status: "rejected", reason: <error> }— the Promise rejected
The outer Promise returned by Promise.allSettled never rejects. You always get the array.
Extracting Results Cleanly
const results = await Promise.allSettled([
fetchUsers(),
fetchMetrics(),
fetchFeatureFlags(),
]);
// Destructure with fallbacks
const [usersResult, metricsResult, flagsResult] = results;
const users = usersResult.status === "fulfilled"
? usersResult.value
: [];
const metrics = metricsResult.status === "fulfilled"
? metricsResult.value
: defaultMetrics;
const flags = flagsResult.status === "fulfilled"
? flagsResult.value
: {};
if (flagsResult.status === "rejected") {
console.warn("Feature flags unavailable, using defaults:", flagsResult.reason.message);
}
renderDashboard({ users, metrics, flags });Helper: Partition Fulfilled and Rejected
A reusable helper for batch operations makes the pattern concise:
// Helper: Split allSettled results into successes and failures
function partitionSettled(results) {
return results.reduce(
(acc, result) => {
if (result.status === "fulfilled") {
acc.fulfilled.push(result.value);
} else {
acc.rejected.push(result.reason);
}
return acc;
},
{ fulfilled: [], rejected: [] }
);
}
// Usage
async function batchFetchUsers(ids) {
const results = await Promise.allSettled(
ids.map((id) => fetchUser(id))
);
const { fulfilled: users, rejected: errors } = partitionSettled(results);
if (errors.length > 0) {
console.warn(`${errors.length}/${ids.length} user fetches failed`);
errors.forEach((e) => console.warn(" -", e.message));
}
return users;
}Practical Pattern: Data Enrichment
A common real-world use case is enriching a list of core objects with optional data from additional sources. Some enrichments may fail without blocking the core flow:
async function enrichArticles(articles) {
const enrichmentResults = await Promise.allSettled(
articles.map(async (article) => {
const [views, comments] = await Promise.all([
fetchViewCount(article.id),
fetchCommentCount(article.id),
]);
return { id: article.id, views, comments };
})
);
return articles.map((article, i) => {
const result = enrichmentResults[i];
if (result.status === "fulfilled") {
return { ...article, ...result.value };
}
// Enrichment failed: return the article as-is
return { ...article, views: null, comments: null };
});
}Practical Pattern: Retry Only Failures
Promise.allSettled makes it easy to identify and retry failed operations:
async function fetchWithRetrySubset(items, fetcher, maxRetries = 2) {
let pending = items;
const succeeded = [];
let attempt = 0;
while (pending.length > 0 && attempt < maxRetries) {
const results = await Promise.allSettled(pending.map(fetcher));
const nextPending = [];
for (let i = 0; i < results.length; i++) {
if (results[i].status === "fulfilled") {
succeeded.push(results[i].value);
} else {
nextPending.push(pending[i]); // Keep for retry
console.warn(`Retrying item ${pending[i].id}:`, results[i].reason.message);
}
}
pending = nextPending;
attempt++;
if (pending.length > 0 && attempt < maxRetries) {
await new Promise((r) => setTimeout(r, 500 * attempt)); // Backoff
}
}
return {
succeeded,
failed: pending, // Items that failed all retries
};
}allSettled vs all: Decision Table
| Scenario | Use |
|---|---|
| Every result is required (payment processing steps) | Promise.all |
| Dashboard with optional data sections | Promise.allSettled |
| Batch import where partial success is acceptable | Promise.allSettled |
| Parallel prerequisite fetches (all required before next step) | Promise.all |
| Logging/analytics fire-and-forget (failures acceptable) | Promise.allSettled |
| Enriching objects with optional metadata | Promise.allSettled |
Combining allSettled With Typed Results
Using TypeScript or JSDoc, you can type the results for strict handling:
/**
* @template T
* @param {Promise<T>[]} promises
* @returns {Promise<{value: T | null, error: Error | null}[]>}
*/
async function settleAll(promises) {
const results = await Promise.allSettled(promises);
return results.map((r) => ({
value: r.status === "fulfilled" ? r.value : null,
error: r.status === "rejected" ? r.reason : null,
}));
}
// Normalized result:
// [
// { value: {...user}, error: null },
// { value: null, error: Error("Not found") },
// ]The Difference From Promise.all in Error Handling
// Promise.all: first rejection shortcuts everything
try {
const results = await Promise.all([p1, p2, p3]);
// Either all three values, or catch fires
} catch (err) {
// You only know WHICH one failed if the Error has identifying info
// The other Promises already ran but their results are lost
}
// Promise.allSettled: always get all outcomes
const results = await Promise.allSettled([p1, p2, p3]);
// Always 3 results, each labeled fulfilled or rejected
// No need for try/catch — the outer Promise never rejectsFor more on Promise.all and the complete concurrency picture, see how to use Promise.all.
Rune AI
Key Insights
- Promise.allSettled never rejects: The outer Promise always fulfills with an array of outcome objects, making try/catch unnecessary at the
allSettledlevel - Every result is a status object:
{ status: "fulfilled", value }or{ status: "rejected", reason }— always checkstatusbefore accessingvalue - Order is preserved:
results[i]always corresponds topromises[i], enabling safe zip-back of results to input items - Use it for optional/partial data: When some operations failing should not block the overall flow — dashboard sections, enrichment, analytics —
allSettledis the right choice overPromise.all - Log rejected results explicitly: Partial failures are easy to miss — always log or track which operations in the batch failed, even if the overall function continues successfully
Frequently Asked Questions
Does Promise.allSettled ever reject?
When was Promise.allSettled introduced?
Can I use Promise.allSettled as a replacement for Promise.all everywhere?
How do I know which input Promise corresponds to which result?
Can I combine Promise.allSettled with Promise.race inside it?
Conclusion
Promise.allSettled is the right tool when partial success is acceptable or expected. It guarantees you always see the full picture — every outcome for every input — without the fail-fast behavior of Promise.all. Use it for enrichment, batch processing, and optional data sources where the core flow should not break because of one failure. Always handle rejections in the settled results explicitly rather than silently ignoring them.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.