JavaScript Promise.all() and Promise.allSettled() in Practice

If you're await-ing async calls one after another instead of using Promise.all(), you're probably wasting time. Literally.
Say you need to hit three APIs and each takes 300ms. Await them sequentially and you're looking at approximately 900ms. Run them with Promise.all() and you're done in 300ms, which is the time of the slowest call.*
*Mostly, since heavy CPU bound processes, such as image processing, can hold up the event loop and make the time cumulative. More on this later.
Key TakeawaysPromise.all()runs independent async operations concurrently, finishing in the time of the slowest call, not the sum of all callsIf any promise rejects, the entirePromise.all()rejects. UsePromise.allSettled()when you need partial resultsJavaScript isn't truly parallel. It's async, non-blocking I/O. CPU-bound work won't benefit
Promise.all() takes an array of promises and returns a single promise that resolves when all of them resolve. You get back an array of Promises, which will resolve to (with await) an array of results, in the same order you passed them in. From the MDN docs: it "takes an iterable of promises as an input, and returns a single Promise that resolves to an array of the results."
How Does Promise.all() Work?
const urls = [
"https://api.example.com/users",
"https://api.example.com/posts",
"https://api.example.com/comments",
];
const results = await Promise.all(urls.map((url) => fetch(url)));
console.log(results); // [Response, Response, Response]
Map over your data, return promises, and Promise.all() collects the results. Think of loading a documentation site where you need the page content, sidebar nav, and search index before anything renders.
The key point is that the promises start executing the moment you create them. Promise.all() isn't launching them. It's just waiting for all of them to finish.

Real-World: Posting to Multiple Social Networks
At my previous company, I built a social media scheduling API. The user writes a post and expects it to go out on X, Facebook, Instagram, and LinkedIn at the same time. Each network has its own auth flow, rate limits, and API quirks, but the user doesn't care about any of that. They want to hit "post" and get results back fast.
Assuming the four networks average 200-400ms each, Promise.all() cuts our p95 response time from over a second to under 400ms.
const postToNetworks = NETWORKS.map((network) => {
return platforms.includes(network.type)
? network.post(auth[network.type], content, mediaUrls, id)
: Promise.resolve(null);
});
const results = await Promise.all(postToNetworks);
Each network fires off independently. The user gets a consolidated response as soon as the slowest network responds. Without Promise.all(), you'd be posting to X, waiting, then Facebook, waiting, then Instagram. Multiply that by thousands of concurrent users and sequential execution isn't just slow, it's a big scaling problem.
What Happens When One Promise Fails?
If any promise rejects, the entire Promise.all() rejects and returns the first error. You don't get partial results. This is fine when you want fail-fast behavior. Say you're fetching data that's all required before you can render a page. If one call fails, there's nothing useful to show anyway.
try {
const [users, posts, comments] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchComments(),
]);
} catch (err) {
// One failed — you won't know which without inspecting the error
console.error("Something failed:", err.message);
}
But what if you're posting to social networks and don't want a Facebook failure to kill your Instagram post? Enter Promise.allSettled().
When Should You Use Promise.allSettled()?
Promise.allSettled() waits for every promise to either resolve or reject, then gives you the status of each one. No short-circuiting.
const results = await Promise.allSettled([
postToTwitter(content),
postToFacebook(content),
postToInstagram(content),
]);
// results:
// [
// { status: "fulfilled", value: { id: "tweet_123" } },
// { status: "rejected", reason: Error("Rate limited") },
// { status: "fulfilled", value: { id: "ig_456" } },
// ]
Now you can handle each result individually. Retry the failures, log the errors, and still return the successful posts to the user. The State of JavaScript 2024 survey found that 47% of developers now use Promise.allSettled(), up significantly from prior years. It's no longer a niche API.
One caveat: You need to be sure to handle the errors of each Promise passed to Promise.allSettled or you might end up with no return and the system hanging. A good way to prevent this is with the AbortController.timeout() function.
Rule of thumb: Use Promise.all() when all results are required. Use Promise.allSettled() when partial success is acceptable.
It's Not Actually Parallel
A typical interview question is: "Does JavaScript run in parallel." Answer: "No, JavaScript doesn't run promises in parallel. It's single-threaded." You got the job!
What's actually happening is asynchronous, non-blocking execution. The event loop kicks off all the I/O operations (API calls, file reads, database queries) and moves on. When each one completes, its callback lands back on the event loop.
The practical effect feels parallel because the I/O operations happen outside the JS thread. But if your "async" work is CPU-bound (parsing a massive JSON blob, image processing as mentioned above), Promise.all() won't help. You'll just be running those tasks sequentially on the same thread. For CPU-bound work, look at Worker threads in Node.js or Web Workers in the browser.
One other thing: don't use Promise.all() when your calls depend on each other. If call B needs the result of call A, they can't run concurrently. Only reach for it when the operations are genuinely independent.
Quick Reference: All Four Promise Methods
Which method do you actually need? Here is a chart with a few extras such as race and any.
| Method | Resolves when | Rejects when | Best for |
|---|---|---|---|
Promise.all() | All fulfill | Any rejects | Fetching required data in parallel |
Promise.allSettled() | All settle | Never | Partial success is fine (social posts, batch ops) |
Promise.race() | First settles | First settles | Timeouts with AbortSignal.timeout() |
Promise.any() | First fulfills | All reject | Fallback chains, redundant sources |
Promise.race() settles with whichever promise finishes first. The classic use case is timeouts. Pair it with AbortController (as mentioned above) to cancel the request cleanly instead of leaving it hanging.
Promise.any() ignores individual rejections and gives you the first success, which is perfect for hitting multiple CDN endpoints and taking whichever responds first.