Handling Multiple Promises

Open the project folder

In our starter files, open the index.html page from this lessons folder:

10.Async-JavaScript > 05.Handling-multiple-promises

Introduction

Once you understand how promises work, handling an async task becomes a lot easier. But what if we have more than one async task at the same time?

This can cause various issues. Do we wait on the first task to complete before moving onto the second one? What if we had multiple tasks, the delay from each could really add up. Also, what if task 10 relied on the value of task 3, but finished first?

To help with some of these problems we have access to some useful Promise methods. We have covered some methods so far, including then, catch, and finally.

The all() method

The upcoming methods are going to deal with multiple promises, starting with the all() method.

The all method loops over multiple promises and return one single Promise. This single Promise resolves to an array of the results. It is useful for gathering multiple pieces of data and aggregating them into one result.

Using Promise.all()

Let’s look at how this works. In the starter folder we have two promises:

const promise1 = fetch('https://dog.ceo/api/breeds/image/random').then(
  function (response) {
    return response.json();
  }
);
const promise2 = new Promise(function (resolve, reject) {
  setTimeout(resolve, 2000);
});

The promise1 variable selects our random image, and promise2 resolves the promise after two seconds. This is a simple example to demonstrate the issue. Imagine we wanted to know if both were a success. Rather than having to check them individually, we can pass them both to Promise.all:

const promise1 = fetch('https://dog.ceo/api/breeds/image/random').then(
  function (response) {
    return response.json();
  }
);
const promise2 = new Promise(function (resolve, reject) {
  setTimeout(resolve, 2000);
});

// add both promises as an array
Promise.all([promise1, promise2])

This takes in any iterable value, that is something we can loop over, such as an array.

As mentioned before, this method also returns a Promise, where we can chain the then and catch methods:

Promise.all([promise1, promise2])
  .then(function (results) {
    console.log(results);
  })
  .catch(function (error) {
    console.log("there was an error: " + error);
  });

After two seconds, we see an array with two results in the console:

0: {message: 'https://images.dog.ceo/breeds/saluki/n02091831_7846.jpg', status: 'success'}
1: undefined

First is the random image, and this has the status of success. Second, after the timeout, this is showing as undefined since the Promise does not return anything. If we want, we could pass a third parameter to setTimeout:

const promise2 = new Promise(function (resolve, reject) {
  setTimeout(resolve, 2000, 'result');
});

This is an optional parameter to pass to the function we are calling, now we will see this return value in place of undefined.

Failed promises

We can see what happens if we fail one of the promises too, by changing resolve to be reject:

setTimeout(reject, 2000, 'result');

Resulting in the following console message:

there was an error: result

We still get the “result” text passed to the error function, but this time, we do not see the random image, just the Promise which rejected.

This all method will resolve if all the promises it takes in resolves successfully, or, if we have one failure. Meaning we either successfully get an array containing all promises, or the first one which has rejected.

The allSettled() method

If this is not what we wanted, and we still wanted all promises to be returned regardless of if there was a failure, we could use a method called allSettled().

This works very similar, we can just change all to be allSettled:

Promise.allSettled([promise1, promise2])

With the follwing result:

0: {status: 'fulfilled', value: {…}}
1: {status: 'rejected', reason: 'result'}

Now we see all promises, regardless of if they are fulfilled or rejected. We could use these results any way we wanted. We could loop over them to handle what to do for failed or successful responses. A use case could be to try again for the failed requests.

The any() and race() methods

Something else we have are two methods called any() and race(). Both will only return one value.

First, we have any:

Promise.any([promise1, promise2])

As this sounds, it will resolve as soon as any of the promises passed to it is fulfilled. This means the first promise which is a success. For our example, we should see the image since the other Promise has a time delay.

Then we have the race method:

Promise.race([promise1, promise2])

This will return the first promise passed to it which is settled. This could be either fulfilled or rejected.

For our example we could again expect the image because this should come back faster than our two second timeout, so regardless of a success or failure, the first one is returned.

To recap, the race method returns the first promise it encounters which resolves or rejects. The any method also returns one promise, but this must be a fulfilled.

Each one of these is very specific to what you want your code to do, and very useful to have these available to us for when needed.