In many real-world applications, we often need to execute multiple asynchronous operations at the same time and wait for all of them to finish before proceeding.
A very common example of this is when an application needs to make multiple HTTP requests in parallel — such as fetching data from different services — and only move forward once all responses are available.
JavaScript provides a powerful utility for this scenario: Promise.all().
A Real-World Example: Selling a Car
Let’s understand this concept using a simple and practical example.
Imagine you want to sell your Mercedes car, and there are three dealers in your city who are interested in buying it.
Each dealer:
- Has a website with an API
- Can give you a price for your car
- Takes a different amount of time to respond
Here’s the situation:
| Dealer | Price Offered | Response Time |
|---|---|---|
| Dealer 1 | $8,000 | 3 seconds |
| Dealer 2 | $12,000 | 5 seconds |
| Dealer 3 | $10,000 | 8 seconds |
The Slow (Sequential) Approach
One possible way to handle this is:
- Ask Dealer 1 → wait 3 seconds
- Ask Dealer 2 → wait another 5 seconds
- Ask Dealer 3 → wait another 8 seconds
- Compare prices and decide
Total waiting time:
3 + 5 + 8 = 16 seconds
This approach works, but it is inefficient because we are waiting unnecessarily.
The Better Approach: Parallel Execution
Instead of waiting for each dealer one by one, we can:
- Send requests to all dealers at the same time
- Wait until all of them respond
- Compare prices and choose the best one
This reduces total waiting time to only the slowest request, which in this case is 8 seconds.
This is exactly where Promise.all() shines.
Modeling This in JavaScript
Let’s imagine we have three functions.
Each function simulates an API call and returns a promise that resolves with a price.
(In a real application, these would be HTTP requests instead of
setTimeout.)
function dealerOne() {
return new Promise(resolve => {
setTimeout(() => resolve(8000), 3000);
});
}
function dealerTwo() {
return new Promise(resolve => {
setTimeout(() => resolve(12000), 5000);
});
}
function dealerThree() {
return new Promise(resolve => {
setTimeout(() => resolve(10000), 8000);
});
}
Each function:
- Returns a promise
- Resolves after a certain delay
- Provides a price as the resolved value
Using Promise.all()
Now let’s run all these promises in parallel.
Promise.all([
dealerOne(),
dealerTwo(),
dealerThree()
]).then(prices => {
console.log(prices);
});
What happens here?
Promise.all()takes one argument → an array- The array contains promises
- It returns a new promise
- That promise:
- Resolves when all promises resolve
- Produces an array of results
Output after ~8 seconds:
[8000, 12000, 10000]
Each value corresponds to the result of each promise in the same order they were passed.
Why This Is Powerful
Using Promise.all() allows you to:
- Run multiple asynchronous tasks in parallel
- Reduce total execution time
- Keep your code clean and readable
- Easily process combined results
Once all values are available, you can easily find the best price or perform further logic.
What If We Pass Non-Promise Values?
Promise.all() is very flexible.
You can also pass non-promise values such as numbers, strings, or booleans.
Example:
Promise.all([1, "hello", true]).then(values => {
console.log(values);
});
Output:
[1, "hello", true]
Why does this work?
- Non-promise values are automatically treated as already resolved promises
- This makes
Promise.all()very flexible and safe to use
What If We Pass an Empty Array?
Promise.all([]).then(result => {
console.log(result);
});
Output:
[]
The promise:
- Resolves immediately
- Returns an empty array
Important Behavior to Remember
Promise.all():
- Accepts any iterable (usually an array)
- Returns a single promise
- Resolves when all promises resolve
- Resolves immediately if the array is empty
Using Promise.all() with a Rejection
Now we run Promise.all() again and attach a .catch() block:
Promise.all([
dealerOne(),
dealerTwo(), // this one rejects
dealerThree()
])
.then(prices => {
console.log(prices);
})
.catch(error => {
console.log(error);
});
What Happens Now?
As soon as any one promise rejects, Promise.all():
- Immediately rejects
- Stops waiting for the remaining promises
- Returns the reason of the first rejection
Output:
Not a suitable car
Even though other promises might still be running, Promise.all() does not wait for them once a rejection occurs.
Important Rule to Remember
Promise.all()fails fast.
This means:
- The first rejection immediately rejects the entire operation.
- Remaining promises are ignored (even if they resolve later).
What If You Don’t Want to Abort Everything?
Sometimes, you don’t want one failure to cancel all results.
You might still want:
- Successful responses
- Failed responses (as data, not errors)
Solution: Handle errors inside each promise
We can do this by attaching a .catch() to each individual promise.
Handling Errors Individually
Promise.all([
dealerOne().catch(error => error),
dealerTwo().catch(error => error),
dealerThree().catch(error => error)
]).then(results => {
console.log(results);
});
Why This Works
.catch()itself returns a promise- If you do not throw inside
.catch(), the promise becomes resolved - This prevents
Promise.all()from failing early
Output:
[8000, "Not a suitable car", 10000]
Explanation:
- First dealer → success →
8000 - Second dealer → rejection → captured as a value
- Third dealer → success →
10000
Now you can safely analyze all results and make a decision.
Fast-Fail Behavior of Promise.all()
Another important characteristic of Promise.all() is that it follows fail-fast behavior.
Let’s say you add another promise that rejects immediately:
Promise.all([
dealerOne(),
dealerTwo(),
dealerThree(),
Promise.reject("Immediate failure")
])
.catch(error => {
console.log(error);
});
What happens?
- The rejection happens instantly
Promise.all()does not wait for other promises- The rejection is returned immediately
Output:
Immediate failure
Even if other promises take 3, 5, or 8 seconds, they are ignored.
Why This Behavior Exists
This design makes sense because:
- In many real-world workflows, one failure invalidates the entire operation
- It avoids unnecessary waiting
- It improves performance and responsiveness
However, it also means developers must handle failures carefully.
