Choosing the Right Concurrent Promise Method in JavaScript
Learn how to choose between Promise.all, Promise.allSettled, and Promise.race based on your error handling requirements and performance constraints.
15 Feb 2026, 13:02 UTC

The Concurrency Decision
When executing multiple asynchronous operations in JavaScript, the primary challenge is not starting the tasks, but determining how the application should react when some tasks succeed and others fail. Choosing the wrong concurrency method can lead to "silent failures," where a single error crashes a batch of unrelated requests, or "zombie promises," where background tasks continue to consume resources after the main operation has already failed.
Comparison of Concurrency Strategies
| Method | Success Condition | Failure Condition | Primary Use Case |
|---|---|---|---|
Promise.all() |
All promises resolve | First rejection triggers immediate failure | Atomic dependencies (All-or-nothing) |
Promise.allSettled() |
All promises finish (regardless of outcome) | Never rejects based on individual promise failure | Independent tasks (Bulk status reporting) |
Promise.race() |
First promise to settle (resolve or reject) | First promise to settle (resolve or reject) | Timeouts or redundant mirrors |
Trade-offs and Engineering Constraints
The "Short-Circuit" Risk with Promise.all
Promise.all is designed for atomic operations. If you are fetching data for a dashboard where the page cannot render without all pieces of data, this is the correct choice. However, it is important to note that Promise.all does not cancel the remaining pending promises when one rejects. The other network requests or timers will continue to run in the background until they resolve or time out, which can lead to wasted server resources.
The Iteration Overhead of Promise.allSettled
Introduced in ES2020, Promise.allSettled is safer for independent tasks (e.g., uploading five separate files). Because it never rejects the aggregate promise, you cannot use a single .catch() block to handle failures. Instead, you must iterate through the resulting array and check the status property of each object (either 'fulfilled' or 'rejected') to determine the outcome.
The Memory Leak Risk with Promise.race
Promise.race is frequently used to implement request timeouts. A common mistake is creating a setTimeout promise that never resolves if the main request wins the race. While the Promise.race call settles, the timer remains in the JavaScript event loop until it expires, which can prevent the process from exiting or cause minor memory leaks in high-throughput environments.
Implementation: Implementing a Request Timeout
The following example demonstrates a practical implementation of Promise.race to prevent a fetch request from hanging indefinitely. This should be run in a Node.js environment (v12+) or a modern browser.
async function fetchWithTimeout(url, ms) {
// Create a promise that rejects after the specified milliseconds
const timeoutPromise = new Promise((_, reject) => {
const timer = setTimeout(() => {
reject(new Error(`Request timed out after ${ms}ms`));
}, ms);
// Note: In a production environment, ensure the timer is cleared
// if the fetch succeeds to avoid keeping the event loop active.
});
try {
// Race the actual fetch against the timeout
const response = await Promise.race([
fetch(url),
timeoutPromise
]);
return await response.json();
} catch (error) {
throw error;
}
}
Validation and Performance Checks
To verify the behavior of these methods, you can run a test script with a mix of resolving and rejecting promises. For Promise.all, a single Promise.reject() in the array should trigger the catch block immediately. For Promise.allSettled, the resulting array should have a length equal to the input array, regardless of how many promises failed.
Performance Limitation: Be cautious when passing very large arrays (thousands of items) to these methods. Because they initiate all promises simultaneously, you may encounter "event loop starvation" or hit OS-level limits on concurrent network sockets. For large datasets, implement a "chunking" or "pooling" mechanism to limit the number of active promises at any given time.
Rollback and State Management
Since these methods are read-only wrappers for promise orchestration, they do not change the state of the application themselves. However, if the promises being raced or aggregated perform mutations (e.g., writing to a database), you must implement a manual rollback mechanism (like a Saga pattern or database transaction) because JavaScript promises cannot be "undone" once they have been dispatched.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.