Promise.all() is one of the most useful tools in JavaScript for handling multiple asynchronous operations.
It allows us to run several promises concurrently and wait until all of them are fulfilled.
But there's something developers often overlook:
When one promise rejects, Promise.all() rejects immediately, but the other operations don't automatically stop.
Let's understand why this matters.
1. The Problem
Imagine fetching user information, notifications, and account settings simultaneously:
const [user, notifications, settings] = await Promise.all([
fetchUser(),
fetchNotifications(),
fetchSettings()
]);
This looks perfectly fine.
But what happens if fetchNotifications() fails?
Promise.all() rejects, and execution moves to the catch block.
However, fetchUser() and fetchSettings() may still be running.
JavaScript doesn't automatically cancel the remaining promises.
This can become problematic when those operations involve expensive requests or side effects.
2. Promise.all() Doesn't Cancel Anything
Consider this example:
const delay = (ms) =>
new Promise((resolve) => setTimeout(resolve, ms));
const task1 = delay(1000).then(() => {
console.log("Task 1 completed");
});
const task2 = delay(2000).then(() => {
throw new Error("Task 2 failed");
});
const task3 = delay(3000).then(() => {
console.log("Task 3 completed");
});
try {
await Promise.all([task1, task2, task3]);
} catch (error) {
console.log(error.message);
}
The output will be:
Task 1 completed
Task 2 failed
Task 3 completed
Notice that Task 3 still completes after Promise.all() has already rejected.
That's because Promise rejection and operation cancellation are two different things.
3. What If You Want Every Result?
Sometimes, failing the entire operation because one promise rejects isn't what you want.
For example, when loading several independent dashboard sections, you may want successful results even if one request fails.
That's where Promise.allSettled() becomes useful:
const results = await Promise.allSettled([
fetchUser(),
fetchNotifications(),
fetchSettings()
]);
for (const result of results) {
if (result.status === "fulfilled") {
console.log("Success:", result.value);
} else {
console.error("Failed:", result.reason);
}
}
Unlike Promise.all(), it waits for every promise to settle, regardless of whether it fulfilled or rejected.
4. What If You Actually Need Cancellation?
For operations that support cancellation, such as fetch(), you can use AbortController.
const controller = new AbortController();
try {
const results = await Promise.all([
fetch("/api/users", {
signal: controller.signal
}),
fetch("/api/notifications", {
signal: controller.signal
}),
fetch("/api/settings", {
signal: controller.signal
})
]);
} catch (error) {
controller.abort();
throw error;
}
If one request rejects, the controller aborts the other requests that are still in progress.
Keep in mind that cancellation is cooperative. It doesn't guarantee that server-side processing has stopped.
5. Which One Should You Use?
| Method | Behavior |
|---|---|
Promise.all() |
Rejects when the first promise rejects |
Promise.allSettled() |
Waits for every promise to settle |
Promise.race() |
Settles with the first settled promise |
Promise.any() |
Fulfills with the first fulfilled promise |
Each method solves a different problem.
Choosing the right one depends on what your application expects when asynchronous operations fail.
Final Thoughts
Asynchronous JavaScript isn't just about running multiple operations at the same time.
It's also about understanding failure behavior, cancellation, and how independent operations affect each other.
A small misunderstanding about promises can create unexpected behavior in production.
Here's a question:
When building a dashboard with multiple independent API requests, would you use Promise.all() or Promise.allSettled()?
And more importantly, why?
Let's discuss.
Top comments (0)