Promise.all()
ES6+Returns a single Promise that resolves when all of the promises in the iterable have resolved, or rejects when any promise rejects.
Syntax
Promise.all(iterable)Parameters
iterable Iterable An iterable of promises
Return Value
A Promise that resolves with an array of all the resolved values
Examples
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);
Promise.all([p1, p2, p3])
.then(values => console.log(values)); 📌 When to Use
Use Promise.all() when several asynchronous operations are independent of each other and you need every one of them to succeed before continuing. The classic case is assembling a page from multiple endpoints — user, permissions, and settings — where rendering with any piece missing would be wrong. Because the operations start before you await the combined promise, they run concurrently: total time equals the slowest request rather than the sum, which routinely turns four sequential 250 ms calls into one 300 ms burst. It is also the standard fix for the 'await in a loop' antipattern: map your items to promises first, then await Promise.all() once. Choose it specifically for its fail-fast contract — the combined promise rejects the moment any input rejects, letting you abort early and show a single error. If partial results are still useful, for example a dashboard where one dead widget should not blank out the others, that same contract works against you and Promise.allSettled() is the better tool; if you need only the first success among alternatives, use Promise.any(). Keep the input list bounded: Promise.all() is designed for a handful of known tasks, not for firing ten thousand simultaneous requests at an API that will rate-limit you.
⚠️ Common Mistakes
Using Promise.all() when partial success is acceptable. One rejection discards every other result — even though the remaining operations keep running to completion, you never see their values. If you need per-item outcomes, Promise.allSettled() reports a status object for each input instead of failing fast.
Passing an empty array and being surprised: it resolves immediately with [], which can skip loading states or emit completion events before any real work happened. Guard dynamically built inputs when an empty list is meaningful to your logic.
Awaiting inside a for loop when the iterations are independent. Each await parks the function until that single item finishes, serializing the work: ten 200 ms requests take two seconds instead of roughly 0.2 seconds. Build the array of promises first with items.map(fn), then await Promise.all() once.
Assuming rejection cancels the other operations. JavaScript promises are not cancellable; the losing requests keep consuming network, memory, and server resources, and their results are silently dropped. Pass an AbortController signal to each fetch and abort in your error handler if you need real cancellation.
Losing track of which operation failed. The rejection reason is just the first error, with no index or label attached. Wrap each promise to tag its failures — p.catch(e => { throw new Error('users request: ' + e.message); }) — when the source matters for debugging.
Firing an unbounded number of concurrent operations, such as userIds.map(fetchUser) over thousands of ids. Browsers cap connections per host and servers rate-limit aggressively, so requests queue up or fail. Process large collections in fixed-size batches or through a concurrency limiter.
✅ Best Practices
Destructure the results array in input order — const [user, posts] = await Promise.all([fetchUser(), fetchPosts()]) — the output order is guaranteed to match the input order regardless of which promise settled first.
Bound your concurrency for large workloads: chunk the input and run Promise.all() per chunk, or use a limiter utility, keeping parallelism at a level the backend can absorb — often five to ten concurrent calls is the sweet spot.
Start the promises before awaiting anything: const a = fetchA(); const b = fetchB(); await Promise.all([a, b]). This makes the concurrency explicit and prevents accidentally serializing the calls by awaiting each one as you create it.
Wrap the call in try/catch (or one .catch) and treat the failure as a single unit: report the first error, abort in-flight siblings via AbortController, and offer one retry for the whole group rather than per-item error states.
Mix values freely when composing: non-promise entries are treated as already fulfilled, which makes it easy to combine cached data with live requests — Promise.all([cachedConfig, fetchUser()]) — without special-casing.
⚡ Performance Notes
Promise.all() itself does no scheduling — the operations begin the moment you create them — it merely subscribes to every input promise and counts down settlements. Its value is wall-clock parallelism for I/O: total latency equals the slowest input instead of the sum, so the speedup over sequential awaits grows with list length and per-item latency. The combinator's own overhead is one result array plus a reaction record per input, delivered through the normal microtask queue, which is negligible even for hundreds of entries. Two real costs deserve attention: memory, since every result is retained until the last input settles (relevant for large payloads); and the fail-fast illusion — after an early rejection the surviving operations still occupy sockets, CPU, and server capacity, invisibly to your code. In CPU-bound work Promise.all() buys nothing: JavaScript is single-threaded, and concurrency only helps when the work is genuinely asynchronous I/O handled off the main thread.
🌍 Real World Example
Dashboard Data Loader
A dashboard bootstrap that needs four resources before it can render anything meaningful. All requests are created together so they run concurrently, the destructured results arrive in input order, and one try/catch treats the group as a single unit of work — if any request fails the caller receives a clean failure flag instead of a half-built dashboard. Total load time is the slowest endpoint rather than the sum of all four, typically a three-to-four-fold improvement over fetching them sequentially.
async function loadDashboard(userId) {
try {
const [user, stats, notifications, settings] = await Promise.all([
fetch(`/api/users/${userId}`).then(r => r.json()),
fetch(`/api/users/${userId}/stats`).then(r => r.json()),
fetch(`/api/users/${userId}/notifications`).then(r => r.json()),
fetch(`/api/users/${userId}/settings`).then(r => r.json())
]);
return {
success: true,
data: { user, stats, notifications, settings }
};
} catch (error) {
return {
success: false,
error: 'Failed to load dashboard data'
};
}
}
// All 4 requests run in parallel, total time = slowest request