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.

JavaScriptintermediate
13 min read

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:

MethodResolves WhenRejects WhenUse Case
Promise.allALL fulfillANY rejectsNeed all results
Promise.allSettledALL settle (either way)NeverNeed all outcomes
Promise.raceFIRST settles (fulfill or reject)FIRST settles (reject)Timeout patterns, first-wins
Promise.anyFIRST fulfillsALL rejectRedundant 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:

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

javascriptjavascript
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

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

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

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

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

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

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

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

javascriptjavascript
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

MethodInput (5 promises)2 Fulfill, 3 Reject5 Fulfill, 0 Reject
Promise.allRuns allRejects (first reject)Fulfills with [5 values]
Promise.allSettledRuns allFulfills with mixed outcomesFulfills with [5 fulfilled]
Promise.raceRuns allSettles with first resultFulfills with first value
Promise.anyRuns allFulfills with first successFulfills with first value

Controlling Concurrency

Promise.all runs everything simultaneously, which can overwhelm an API with rate limits. Batch your concurrent operations:

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

javascriptjavascript
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

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

javascriptjavascript
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

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

Frequently Asked Questions

Does Promise.all guarantee order in the result array?

Yes. The result array always matches the input array order regardless of which Promise resolved first. This is a guarantee from the specification.

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

Yes. Non-Promise values are treated as already-fulfilled Promises with that value. `Promise.all([1, fetchData(), "hello"])` is valid โ€” positions 0 and 2 immediately resolve with `1` and `"hello"`.

What happens to uncompleted Promises after Promise.all rejects?

They continue running to completion or rejection. `Promise.all` does not and cannot cancel them โ€” Promises have no built-in cancellation. The results are ignored. If you need to cancel, use `AbortController` (for `fetch`) or implement your own cancellation signal in the Promises.

When should I prefer Promise.race over Promise.any?

Use `Promise.race` when you care about the first settled result (including rejection). Use `Promise.any` when you only care about the first success and want to keep trying the others after one fails. Timeout patterns use `Promise.race` because a timed-out rejection IS a meaningful first result.

Is Promise.all more performant than sequential awaits?

For I/O-bound operations (network requests, file reads), yes โ€” significantly. The total time becomes the maximum individual time rather than the sum. For CPU-bound operations in JavaScript (synchronous computation), there is no difference because JavaScript is single-threaded. `Promise.all` gives concurrency for async I/O, not parallelism for synchronous work.

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.