Learnitweb

Executing Multiple Promises in Parallel Using Promise.all()

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:

DealerPrice OfferedResponse Time
Dealer 1$8,0003 seconds
Dealer 2$12,0005 seconds
Dealer 3$10,0008 seconds

The Slow (Sequential) Approach

One possible way to handle this is:

  1. Ask Dealer 1 → wait 3 seconds
  2. Ask Dealer 2 → wait another 5 seconds
  3. Ask Dealer 3 → wait another 8 seconds
  4. 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.