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.

7 min read

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.

javascriptjavascript
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.

javascriptjavascript
const results = await Promise.all(iterable);
ParameterDescription
iterableAn iterable (usually an array) of promises
ReturnsA 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:

javascriptjavascript
// 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 vs parallel execution timing
Sequential (await one at a time)Parallel (Promise.all)
Total time for 3 requests at 200ms each~600ms~200ms
Requests in flight at once13
Scales with more requestsLinearly slowerStays 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.

javascriptjavascript
// 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.

javascriptjavascript
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.

javascriptjavascript
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.

javascriptjavascript
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.

javascriptjavascript
// 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.

javascriptjavascript
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 object

This 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.

javascriptjavascript
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.

javascriptjavascript
// 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.

javascriptjavascript
// 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 name

The 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

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.
RunePowered by Rune AI

Frequently Asked Questions

What happens if one promise in Promise.all rejects?

Promise.all rejects immediately with that error. The other promises continue running, but their results are discarded. If you need partial results, use Promise.allSettled instead.

Does Promise.all preserve the order of results?

Yes. The results array matches the order of the input promises, not the order they completed in. The first result corresponds to the first promise, the second to the second, and so on.

Can I pass non-promise values to Promise.all?

Yes. Non-promise values are wrapped in Promise.resolve() and treated as instantly fulfilled promises. This is useful for mixing static data with async requests.

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.