Running Promises In Parallel With Promise All

Three independent API calls, awaited one after another, waste real time waiting on requests that never depended on each other. Promise.all runs them together instead, and still hands back results in the order they were requested.

July 12, 20265 min read20 / 22

The last post got a single value safely out of an async function. This one is about three values at once, and why awaiting them one at a time is usually the wrong move.

The Naive, Sequential Version

Say the goal is a function that takes three country names and logs their three capital cities. await makes this look deceptively simple, one line per country, all reusing the getJSON helper from post 10.

JavaScript · Live Editor
Loading editor...

Hit Run and wait for it. ['Tokyo', 'Brasilia', 'Nairobi'] prints, wrapped in try/catch like every async function should be. It works, and at a glance it even looks reasonable.

Why Sequential Is the Wrong Shape Here

Look closer at what actually happens: the second await doesn't start until the first one has completely finished, and the third doesn't start until the second has. That ordering would make sense if Brazil's lookup somehow needed Japan's result first, but it doesn't. These three requests have nothing to do with each other, and yet the code makes the second one wait for the first anyway.

If each request takes half a second, three of them run back to back cost a full 1.5 seconds, when the actual work could fit in half a second if it all happened at once. On a slow connection that gap gets worse, not better, since every request pays its own round-trip delay on top of the previous one's.

Three independent requests: run one after another the total time is the sum of all three, run together the total time is only as long as the slowest one. ExpandThree independent requests: run one after another the total time is the sum of all three, run together the total time is only as long as the slowest one.

Promise.all Runs Them Together

Promise.all is a static method on the Promise constructor, a combinator, since its whole job is combining several Promises into one. It takes an array of Promises and returns a single new Promise that settles once every one of them has.

The fix is building that array of Promises first, without awaiting any of them individually, then awaiting the combined result:

JavaScript · Live Editor
Loading editor...

Hit Run. Same ['Tokyo', 'Brasilia', 'Nairobi'] result, but all three requests fire at the same moment now instead of queueing behind each other. getJSON(...) still returns a Promise either way, the only change is that none of them get awaited on their own, they get handed to Promise.all as an array and awaited together as one unit.

The Result Order Matches the Input Order, Not the Finish Order

data here is an array of three results, [japanResult, brazilResult, kenyaResult], in exactly that order. It stays that way even if Kenya's request happens to come back first and Japan's comes back last. Promise.all hands back results in the same order the Promises were passed in, completely independent of which one actually finishes first. That's what makes data.map((country) => country[0].capital) safe: position 0 is always Japan's result, never whichever one happened to be fastest.

One Rejection Fails the Whole Thing

Promise.all has one sharp edge worth knowing before relying on it: if any single Promise in the array rejects, the entire Promise.all rejects immediately, even if the other two would have succeeded.

JavaScript · Live Editor
Loading editor...

Hit Run. Only the error prints, never the two successful values, they get discarded the moment the middle Promise rejects. This behavior has a name: Promise.all short-circuits on the first rejection. One bad request is enough to take down the whole batch, which matters most when the calls genuinely don't depend on each other but the code still needs all three to succeed to make sense of the result.

When to Actually Reach for This

Any time a function needs to fire off several independent asynchronous operations, ones that don't rely on each other's results, running them through Promise.all instead of one await after another is close to a free win. The requests overlap instead of stacking up, the total wait time drops to whatever the slowest single request takes, and the code barely changes shape to get it. Promise.all also works fine handed off to .then() instead of await, for anyone still consuming a particular chain that way.

Promise.all isn't the only combinator built on top of Promises either, just the one worth reaching for first.

The Essentials

  1. Awaiting independent requests one after another wastes time, each one waits for the previous one to finish even though nothing connects them.
  2. Promise.all takes an array of Promises and returns one Promise that settles once they all have, running every one of them at the same time.
  3. Results come back in the same order the Promises were passed in, never in whichever order they actually finished.
  4. One rejected Promise in the array rejects the entire Promise.all immediately, discarding every other result, a behavior called short-circuiting.
  5. Promise.all is called a combinator function because its whole purpose is combining multiple Promises into a single one.