Promise All in JavaScript: Complete Guide
Promise.all runs multiple promises in parallel and waits for all of them. Learn how to batch API calls, handle failures, and combine results safely.
Promise.all takes an array of promises and returns a single promise. That promise resolves when every promise in the array has resolved, with an array of all the resolved values. If any promise rejects, the entire call rejects immediately with that error.
const [user, posts, settings] = await Promise.all([
fetch("/api/user").then((r) => r.json()),
fetch("/api/posts").then((r) => r.json()),
fetch("/api/settings").then((r) => r.json()),
]);
console.log(user.name);
console.log(`${posts.length} posts loaded`);All three requests start at the same time. The function waits for all of them to finish, then destructures the results into named variables. This is the cleanest way to run independent parallel requests.
Promise.all syntax
Stripped down to its signature, Promise.all takes one argument and returns one promise, which makes it easy to drop into any async function.
const results = await Promise.all(iterable);| Parameter | Description |
|---|---|
| iterable | An iterable (usually an array) of promises |
| Returns | A promise that resolves to an array of resolved values, or rejects with the first error |
The results array preserves the original order. If the third promise finishes first, its result still appears at index 2.
Sequential vs parallel: the performance difference
Three independent fetches, each taking 200ms:
// Sequential: ~600ms total
const user = await fetchUser(1); // 200ms
const posts = await fetchPosts(1); // 200ms
const comments = await fetchComments(1); // 200ms
// Done in 600ms
// Parallel with Promise.all: ~200ms total
const [user, posts, comments] = await Promise.all([
fetchUser(1), // Starts immediately
fetchPosts(1), // Starts immediately
fetchComments(1), // Starts immediately
]);
// Done in 200ms (the slowest of the three)| Sequential (await one at a time) | Parallel (Promise.all) | |
|---|---|---|
| Total time for 3 requests at 200ms each | ~600ms | ~200ms |
| Requests in flight at once | 1 | 3 |
| Scales with more requests | Linearly slower | Stays flat, bounded by the slowest request |
The difference is 3x in this example. With more requests or slower endpoints, the gap grows linearly with the sequential approach but stays flat when the calls run at the same time.
The critical detail: promises start running the moment they are created, not when they are awaited. This is why running them together works: all three promises begin their work immediately when the array is built.
// These promises start running NOW, even before Promise.all is called
const userPromise = fetchUser(1);
const postsPromise = fetchPosts(1);
const commentsPromise = fetchComments(1);
// This just waits for them all
const [user, posts, comments] = await Promise.all([
userPromise, postsPromise, commentsPromise,
]);Handling errors with Promise.all
If any promise rejects, the call rejects immediately. The other promises keep running, but their results are lost.
try {
const results = await Promise.all([
fetchUser(1),
fetchUser(-1), // This will reject
fetchUser(2),
]);
} catch (error) {
console.error("Batch failed:", error.message);
// The error from fetchUser(-1) is caught here
// fetchUser(1) and fetchUser(2) results are discarded
}The rejection is fail-fast. The moment one promise rejects, the whole call rejects. It does not wait for the others.
For cases where you need results from the promises that succeeded, use Promise.allSettled instead. See Using Promise allSettled for Reliable JS APIs. For the fundamentals of working with promises, see JavaScript Promises: Complete Beginner Guide.
Practical pattern: batch processing
When you need to process an array of items through an async operation, map them to promises and pass the whole array in.
async function fetchUsersByIds(ids) {
const userPromises = ids.map((id) =>
fetch(`/api/users/${id}`).then((r) => r.json())
);
return Promise.all(userPromises);
}
const users = await fetchUsersByIds([1, 2, 3, 4, 5]);
console.log(`Loaded ${users.length} users`);All five requests start at the same time. The function resolves when all five complete.
Be careful with large arrays. If you map 10,000 IDs to 10,000 fetch calls, you open 10,000 concurrent connections, which most servers and browsers will refuse or throttle long before that number. For large batches, split the work into fixed-size chunks and run one chunk at a time.
async function batchWithLimit(items, asyncFn, limit = 5) {
const results = [];
for (let i = 0; i < items.length; i += limit) {
const chunk = items.slice(i, i + limit);
results.push(...(await Promise.all(chunk.map(asyncFn))));
}
return results;
}Calling it looks like a normal batch call, just with an extra argument for how many requests to run at once.
// Process 100 users, 5 at a time
const users = await batchWithLimit(userIds, (id) =>
fetch(`/api/users/${id}`).then((r) => r.json())
);This processes items in chunks of 5, waiting for each chunk to complete before starting the next. It balances speed with server load. Raising the limit finishes faster but risks hitting a server's rate limiter or a browser's per-host connection cap; lowering it is slower but safer against both.
Combining static data with async results
Promise.all wraps non-promise values in Promise.resolve automatically, so you can mix static data with async requests.
const defaultSettings = { theme: "light", notifications: true };
const [user, posts, settings] = await Promise.all([
fetchUser(1),
fetchPosts(1),
defaultSettings, // Wrapped in Promise.resolve() automatically
]);
// settings is the defaultSettings objectThis is useful when a function always needs to return a consistent shape but some fields come from APIs and others are local, such as a page that combines a fetched user profile with a hardcoded set of feature flags that never changes.
Promise.all with async/await
Awaiting it directly with array destructuring is the cleanest pattern.
async function loadPageData(userId) {
try {
const [profile, activity, recommendations] = await Promise.all([
api.get(`/users/${userId}/profile`),
api.get(`/users/${userId}/activity`),
api.get(`/users/${userId}/recommendations`),
]);
return { profile, activity, recommendations };
} catch (error) {
console.error("Failed to load page data:", error.message);
return { profile: null, activity: [], recommendations: [] };
}
}The try...catch wraps the entire parallel load. If any request fails, all results are discarded and the fallback is returned.
Common Promise.all mistakes
Creating promises inside an await loop instead of using Promise.all. This is the sequential-vs-parallel trap.
// Mistake: sequential (slow)
const users = [];
for (const id of ids) {
users.push(await fetchUser(id));
}
// Correct: parallel (fast)
const users = await Promise.all(ids.map((id) => fetchUser(id)));The sequential version does the exact same work but waits for each request to finish before starting the next, turning three 200ms requests into 600ms of wall-clock time for no benefit.
Not handling the rejection. An uncaught rejection becomes an unhandled promise rejection. Always wrap in try...catch or chain a catch handler.
Assuming Promise.all waits for the first rejection. It does. But it does not cancel the other promises. They continue running. If those promises have side effects (creating database records, sending emails), those side effects still happen.
This distinction matters most for writes. A batch of three POST requests where the second one fails still lets the first and third complete on the server, even though your code only sees the rejection and never reads their results. Reads are usually safe to ignore in this scenario, but a failed batch of writes can leave your data in a partially-applied state that is worth planning for separately.
Forgetting that .map() with async functions returns an array of promises. If you map over an array with an async callback but do not pass the result through Promise.all, you get an array of pending promises, not results.
// Mistake: .map returns promises, not values
const users = ids.map(async (id) => await fetchUser(id));
console.log(users[0].name); // undefined (users[0] is a Promise)
// Correct
const users = await Promise.all(ids.map((id) => fetchUser(id)));
console.log(users[0].name); // The actual nameThe fix is the same one function call away: pass the array of promises that map produces into Promise.all, and await the whole thing before reading any property off the results.
Rune AI
Key Insights
- Promise.all takes an array of promises and resolves when every promise resolves.
- If any promise rejects, Promise.all rejects immediately with that error.
- The results array preserves input order, not completion order.
- Use array destructuring with await Promise.all for clean parallel variable assignment.
- Promise.all is for all-or-nothing. Use Promise.allSettled when partial success is okay.
Frequently Asked Questions
What happens if one promise in Promise.all rejects?
Does Promise.all preserve the order of results?
Can I pass non-promise values to Promise.all?
Conclusion
Promise.all is the right tool when you have multiple independent async operations and you need all of them to succeed. Pair it with array destructuring for clean parallel execution, and use Promise.allSettled when partial failure is acceptable.
More in this topic
Using Decorators for Logging in JS Architecture
Learn how JavaScript decorators wrap class methods to add logging without touching the original code, including setup with Babel and how execution order works.
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.