How to Use Promise.all in JavaScript: Complete Tutorial
Master Promise.all, Promise.allSettled, Promise.race, and Promise.any in JavaScript. Learn parallel vs sequential execution, error handling strategies, and practical patterns for concurrent async operations.
When you have multiple independent async operations, executing them sequentially wastes time. Promise.all and its siblings enable concurrent execution of multiple Promises, waiting for all (or one, or the first settled) to complete. Understanding each method's behavior — especially how errors are handled — is essential for writing efficient async JavaScript.
The Four Promise Concurrency Methods
JavaScript provides four static Promise methods for handling multiple Promises:
| Method | Resolves When | Rejects When | Use Case |
|---|---|---|---|
Promise.all | ALL fulfill | ANY rejects | Need all results |
Promise.allSettled | ALL settle (either way) | Never | Need all outcomes |
Promise.race | FIRST settles (fulfill or reject) | FIRST settles (reject) | Timeout patterns, first-wins |
Promise.any | FIRST fulfills | ALL reject | Redundant sources, first-success |
Promise.all: Parallel Fan-Out
Promise.all(iterable) starts all Promises simultaneously and resolves with an array of their results in the same order as the input:
async function loadDashboard(userId) {
// Sequential (slow): each waits for the previous
// const user = await fetchUser(userId); // 200ms
// const posts = await fetchPosts(userId); // 300ms
// const stats = await fetchStats(userId); // 150ms
// Total: ~650ms
// Parallel (fast): all run at the same time
const [user, posts, stats] = await Promise.all([
fetchUser(userId), // 200ms
fetchPosts(userId), // 300ms
fetchStats(userId), // 150ms
]);
// Total: ~300ms (the slowest one)
return { user, posts, stats };
}The result array preserves input order regardless of which Promise fulfilled first.
Basic Error Handling With Promise.all
Promise.all uses fail-fast semantics — one rejection cancels the entire result:
async function loadAll(userIds) {
try {
const users = await Promise.all(
userIds.map((id) => fetchUser(id))
);
return users;
} catch (error) {
// ONE rejection from any fetchUser call lands here
console.error("Failed to load all users:", error.message);
throw error;
}
}Important: the other Promises are NOT cancelled. They continue running. Promise.all just stops waiting for the others and delivers the first rejection immediately. The ongoing Promises settle or reject independently — their results are ignored.
Promise.all With Transformation
// Example: fetch and transform multiple pages of data
async function fetchAllPages(baseUrl, pageCount) {
const pages = await Promise.all(
Array.from({ length: pageCount }, (_, i) =>
fetch(`${baseUrl}?page=${i + 1}`).then((r) => r.json())
)
);
// pages = [page1Data, page2Data, page3Data, ...]
return pages.flat(); // Flatten all pages into one array
}Promise.allSettled: Get Every Result
Promise.allSettled never rejects. It waits for ALL Promises to settle and returns an array of result objects:
const results = await Promise.allSettled([
fetchUser(1),
fetchUser(2),
fetchUser(999), // This one will probably reject (not found)
]);
// results is an array of objects:
// [
// { status: "fulfilled", value: { id: 1, name: "Alice" } },
// { status: "fulfilled", value: { id: 2, name: "Bob" } },
// { status: "rejected", reason: Error("User 999 not found") },
// ]
const successful = results
.filter((r) => r.status === "fulfilled")
.map((r) => r.value);
const failed = results
.filter((r) => r.status === "rejected")
.map((r) => r.reason.message);
console.log(`${successful.length} succeeded, ${failed.length} failed`);allSettled vs all: Which to Use?
// Use Promise.all when: ALL results are required for the operation to make sense
// Example: You need the config AND the user to initialize the app
const [config, user] = await Promise.all([
loadConfig(),
authenticateUser(),
]);
// Use Promise.allSettled when: Partial results are acceptable
// Example: Enriching a list of users with optional profile data
const enrichmentResults = await Promise.allSettled(
users.map((u) => fetchEnrichment(u.id))
);
const enriched = users.map((u, i) => ({
...u,
extra: enrichmentResults[i].status === "fulfilled"
? enrichmentResults[i].value
: null,
}));Promise.race: First to Settle Wins
Promise.race resolves or rejects with the outcome of the FIRST Promise to settle:
// Classic use case: timeout wrapper
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Timed out after ${ms}ms`)), ms)
);
return Promise.race([promise, timeout]);
}
// Usage
try {
const data = await withTimeout(fetchSlowData(), 5000);
console.log(data);
} catch (err) {
if (err.message.includes("Timed out")) {
showCachedData();
} else {
showError(err);
}
}race for Fastest Response
// Try multiple endpoints, use whichever responds first
async function fetchFromFastestServer(data) {
return Promise.race([
fetch("https://us.api.example.com/data").then((r) => r.json()),
fetch("https://eu.api.example.com/data").then((r) => r.json()),
fetch("https://ap.api.example.com/data").then((r) => r.json()),
]);
}Keep in mind that the "losing" requests still complete — they just generate ignored results. Consider aborting them with AbortController if you need to avoid unnecessary network work:
async function fetchFastest(urls) {
const controllers = urls.map(() => new AbortController());
try {
const result = await Promise.race(
urls.map((url, i) =>
fetch(url, { signal: controllers[i].signal }).then((r) => r.json())
)
);
// Cancel the other requests
controllers.forEach((c) => c.abort());
return result;
} catch (err) {
controllers.forEach((c) => c.abort());
throw err;
}
}Promise.any: First to Succeed
Promise.any is like Promise.race but only cares about fulfillments:
// Succeeds as soon as ANY Promise fulfills
// Only fails if ALL Promises reject
async function loadFromAnySource(sources) {
try {
const data = await Promise.any(
sources.map((source) => fetchFromSource(source))
);
return data;
} catch (err) {
// err is an AggregateError — contains all individual errors
console.error("All sources failed:", err.errors);
throw err;
}
}AggregateError From Promise.any
try {
await Promise.any([
Promise.reject(new Error("Source 1 down")),
Promise.reject(new Error("Source 2 slow")),
Promise.reject(new Error("Source 3 unavailable")),
]);
} catch (err) {
console.log(err instanceof AggregateError); // true
console.log(err.message); // "All promises were rejected"
console.log(err.errors); // [Error("Source 1 down"), Error("Source 2 slow"), ...]
}Comparison Table
| Method | Input (5 promises) | 2 Fulfill, 3 Reject | 5 Fulfill, 0 Reject |
|---|---|---|---|
Promise.all | Runs all | Rejects (first reject) | Fulfills with [5 values] |
Promise.allSettled | Runs all | Fulfills with mixed outcomes | Fulfills with [5 fulfilled] |
Promise.race | Runs all | Settles with first result | Fulfills with first value |
Promise.any | Runs all | Fulfills with first success | Fulfills with first value |
Controlling Concurrency
Promise.all runs everything simultaneously, which can overwhelm an API with rate limits. Batch your concurrent operations:
// Process in batches of N concurrent requests
async function processInBatches(items, batchSize, processor) {
const results = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchResults = await Promise.all(batch.map(processor));
results.push(...batchResults);
// Optional: add a delay between batches to respect rate limits
if (i + batchSize < items.length) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
}
return results;
}
// Usage: process 100 items, 10 at a time
const allResults = await processInBatches(hundredItems, 10, fetchItemData);Common Patterns
Parallel Fetch With Default Values
async function loadUserPage(userId) {
const [user, posts, stats] = await Promise.allSettled([
fetchUser(userId),
fetchPosts(userId),
fetchStats(userId),
]).then((results) =>
results.map((r, i) => {
if (r.status === "fulfilled") return r.value;
console.warn(`Part ${i} failed:`, r.reason.message);
return [null, [], {}][i]; // Default values per position
})
);
return { user, posts: posts || [], stats: stats || {} };
}Promise.all With Cleanup
async function openMultipleConnections(configs) {
const connections = await Promise.all(
configs.map((c) => openConnection(c))
);
// Return connections with a cleanup function
return {
connections,
closeAll: () => Promise.all(connections.map((c) => c.close())),
};
}
const { connections, closeAll } = await openMultipleConnections(dbConfigs);
try {
await processWithConnections(connections);
} finally {
await closeAll();
}How the Event Loop Handles Promise.all
Promise.all schedules all input Promises to run in the current event loop tick. All callbacks are registered immediately. The resolution of each Promise schedules microtasks internally. Promise.all resolves after all its internal microtasks have settled.
console.log("start");
Promise.all([
Promise.resolve(1), // Schedules microtask immediately
Promise.resolve(2), // Schedules microtask immediately
Promise.resolve(3), // Schedules microtask immediately
]).then(([a, b, c]) => {
console.log(a, b, c); // "1 2 3" — all settle before .then fires
});
console.log("end");
// Output: "start", "end", "1 2 3"
// (The .then fires after synchronous code clears, as microtasks)Rune AI
Key Insights
- Promise.all is fail-fast: One rejection immediately rejects the whole result — use it when every result is required; use Promise.allSettled when partial success is acceptable
- Order is preserved in Promise.all results: The results array always matches input order regardless of which Promise settled first
- Promise.race reacts to the first settlement: This includes both fulfillment and rejection, making it perfect for timeout wrappers where a timeout rejecting first IS the intended behavior
- Promise.any ignores rejections until all fail: It is the "try everything, use the first success" pattern, and its AggregateError contains all individual failure reasons
- Concurrent does not mean cancelled: When Promise.all (or race or any) stops waiting for the remaining Promises, those Promises continue running — only their results are ignored, not the async work itself
Frequently Asked Questions
Does Promise.all guarantee order in the result array?
Can I pass non-Promise values to Promise.all?
What happens to uncompleted Promises after Promise.all rejects?
When should I prefer Promise.race over Promise.any?
Is Promise.all more performant than sequential awaits?
Conclusion
Promise.all is the primary tool for running independent async operations concurrently, reducing total wait time from the sum of all durations to the maximum. Promise.allSettled handles partial failure gracefully by returning every outcome. Promise.race and Promise.any handle first-wins scenarios with different definitions of "winning." For deeply-chained individual operations, Promise chaining and rejection handling remain the building blocks, while these concurrency methods sit on top.
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.