Why Promise.all() Isn’t Always the Best Choice for Independent API Calls
Async/await is powerful — but it’s easy to assume it magically makes your code fast and reliable. I’ve seen this first-hand in real-world scenarios. One common issue when working with multiple API calls is sequential execution, which can lead to unnecessarily long load times. For example, consider the following code: const response1 = await fetchA(); const response2 = await fetchB(); const response3 = await fetchC(); In this case, each API call waits for the previous one to finish before starting, which means the total response time is the sum of all the individual call times To overcome this, we can run the API calls in parallel instead of sequentially. The solution is to use Promise.all(): const [response1, response2, response3] = await Promise.all([ fetchA(), fetchB(), fetchC(), ]); The total response time dropped immediately. Instead of waiting for each other, all three requests were made simultaneously. That felt like a huge win. But here’s where things can get tricky… The Hidden Risk with Promise.all() While Promise.all() looks like the perfect solution to run tasks in parallel, it has a major caveat: If any one of the promises rejects, the whole Promise.all() rejects — even if the other calls succeed. For example: const [response1, response2, response3] = await Promise.all([ fetchA(), fetchB(), // let's say this fails fetchC(), ]); If fetchB() throws an error, you won’t get the results of fetchA() or fetchC(), even if they succeeded. The entire batch fails. So if your API calls are independent and you still want results from the ones that succeed, Promise.all() may not be the best fit. The Safer Alternative: Promise.allSettled() When you care about completing all tasks, regardless of success or failure, use Promise.allSettled(): const results = await Promise.allSettled([ fetchA(), fetchB(), fetchC(), ]); results.forEach((result, index) => { if (result.status === 'fulfilled') { console.log(`Response ${index + 1}:`, result.value); } else { console.error(`Error in API ${index + 1}:`, result.reason); } }); When to Use What? Use **Promise.all()** when all calls must succeed for the task to proceed. Use **Promise.allSettled()** when calls are independent and can fail individually without affecting the rest. Use individual try/catch blocks around each await call when you need granular error handling for each API response. ## Final Thoughts Async/await makes async logic cleaner, but that doesn’t guarantee efficiency or fault-tolerance. Knowing when to parallelize and how to handle failures is key. So the next time you’re making multiple API calls: Check if they’re independent Decide how you want to handle partial failures Choose Promise.all(), Promise.allSettled(), or a manual await based on that Tiny changes. Huge impact. ✍️ Have you faced similar async challenges in your codebase? Let’s chat in the comments!

Async/await is powerful — but it’s easy to assume it magically makes your code fast and reliable. I’ve seen this first-hand in real-world scenarios.
One common issue when working with multiple API calls is sequential execution, which can lead to unnecessarily long load times. For example, consider the following code:
const response1 = await fetchA();
const response2 = await fetchB();
const response3 = await fetchC();
In this case, each API call waits for the previous one to finish before starting, which means the total response time is the sum of all the individual call times
To overcome this, we can run the API calls in parallel instead of sequentially. The solution is to use Promise.all()
:
const [response1, response2, response3] = await Promise.all([
fetchA(),
fetchB(),
fetchC(),
]);
The total response time dropped immediately. Instead of waiting for each other, all three requests were made simultaneously.
That felt like a huge win. But here’s where things can get tricky…
The Hidden Risk with Promise.all()
While Promise.all()
looks like the perfect solution to run tasks in parallel, it has a major caveat:
If any one of the promises rejects, the whole
Promise.all()
rejects — even if the other calls succeed.
For example:
const [response1, response2, response3] = await Promise.all([
fetchA(),
fetchB(), // let's say this fails
fetchC(),
]);
If fetchB()
throws an error, you won’t get the results of fetchA()
or fetchC()
, even if they succeeded. The entire batch fails.
So if your API calls are independent and you still want results from the ones that succeed, Promise.all()
may not be the best fit.
The Safer Alternative: Promise.allSettled()
When you care about completing all tasks, regardless of success or failure, use Promise.allSettled()
:
const results = await Promise.allSettled([
fetchA(),
fetchB(),
fetchC(),
]);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Response ${index + 1}:`, result.value);
} else {
console.error(`Error in API ${index + 1}:`, result.reason);
}
});
When to Use What?
- Use
**Promise.all()**
when all calls must succeed for the task to proceed. - Use
**Promise.allSettled()**
when calls are independent and can fail individually without affecting the rest. -
Use individual
try/catch
blocks around each await call when you need granular error handling for each API response. ## Final Thoughts Async/await makes async logic cleaner, but that doesn’t guarantee efficiency or fault-tolerance. Knowing when to parallelize and how to handle failures is key.
So the next time you’re making multiple API calls:
- Check if they’re independent
- Decide how you want to handle partial failures
- Choose
Promise.all()
,Promise.allSettled()
, or a manualawait
based on that
Tiny changes. Huge impact.
✍️ Have you faced similar async challenges in your codebase? Let’s chat in the comments!