Dealing with multiple Promises in JavaScript

Edvinas Daugirdas
The Startup
Published in
6 min readNov 6, 2019

--

Photo by Andrew Seaman on Unsplash

ECMAScript 2015 introduced Promises into JavaScript world — the days of callback hell were over. We adopted them quickly and soon you could see promises almost in every new code base.

If you’re like me you’re working with promises on a daily basis — you’re fetching resources, maybe you’re dealing with a library that uses them or maybe you have your own little functions that need to handle asynchronous operations.

Usually, we only need to handle only one asynchronous operation, rarely do we encounter such circumstances that we need to use multiple promises. And to be honest, it can be a bit tricky if you’re dealing with that for the first time as there are several different ways of doing that. Let’s examine what techniques and methods we can use to make our lives a bit easier.

Promise.all()

Promise.all() takes an iterable (such as Array) and returns a single promise that resolves when all of the promises have resolved. Let’s see it in action:

We created two promises: promise1 resolves immediately with a string of “Hello” and promise2 resolves after a second with the string of “World”. Easy enough. It’s really important to note that there is no ordering in execution of the given promises — in some computers they may be executed in parallel, but on others they may be executed serially.

Keeping that in mind we can see how this method can be helpful, for example, if we need to fetch data from different points and they’re not dependent on each other (that means that we’re not interested in the sequence of execution) we can use Promise.all() to do that. But, there are some things that we need to keep in mind when using this method.

Promise.all() has a fail-fast behavior. It means that if one of the promises is rejected then the promise returned from Promise.all() is rejected as well.

We’ve added a new promise promise3 , which is being rejected after two seconds. As you can see as soon as we’re hitting reject Promise.all() will also reject immediately. It doesn’t matter if the other promises would have resolved successfully and in what order the actions happened, one reject is all that it takes. But, we can change this behavior by handling possible rejections like so:

We’re using almost exactly the same code as above, the only difference is that we’ve added map(p => p.catch(e => e)) after Promise.all() , where we’re mapping possible rejections. We could also done it inline (since we know which promise is rejecting), but that probably won’t be the case in a real world scenario:

Promise.all([promise1, promise2, promise3.catch(e => e)])

But what if I told that there might a better way of handling this sort of scenario? Let’s talk about Promise.allSettled() .

Promise.allSettled()

Promise.allSettled() is really similar to Promise.all() . It also takes an iterable and returns a promise that resolves after all of the given promises either have resolved or rejected, with an array of objects which describe the outcome of each promise.

In simple words, it’s Promise.all() without fail-fast behavior and also a bit different return value. But why the different return value? Well, for us to understand whether the provided promise was rejected or resolved a simple value is usually not enough. That’s why instead of just returning the value from a promise Promise.allSettled() returns an object. Example:

Again we’re using the same example as above, the only different is that we’ve replaced Promise.all() with Promise.allSettled(). We don’t need to catch possible errors anymore and the returned value has now become an array of objects with possible values of:

{ status: 'fullfilled', value: 'value' }
{ status: 'rejected', reason: 'reason' }

By using status we can explicitly tell whether the promise has been rejected or resolved.

Unfortunately we can’t simply use Promise.allSettled() in production as of right now, as it’s currently in stage 4 draft mode. I’m pretty sure you can find a polyfill if you want to (although it’s reasonably easy to it write yourself, since we know how it works). It will become a standard feature of next iteration of ECMAScript and is expected to be publicized in 2020. It’s also worth mentioning that there are already some browsers that are supporting it.

Promise.race()

Promise.race() is a bit different although very similar to previous methods that we’ve discussed. It also takes in an iterable as an argument and returns a promise that either resolves or rejects as soon as one of the promises are either resolved or rejected. Example when both of the promises are resolved:

We provide two promises, promise1 and promise2promise1 resolves after 2 seconds and promise2 resolves after 3 seconds, so naturally only see “First promise” logged out, since it was resolved first. This works the same with rejections:

Almost identical example as above, but now we’re rejecting instead of resolving the first promise.

As for real world scenarios imagine you have a request/computation that might take really long time to resolve and you want to wait maximum of 5 seconds. You can leverage Promise.race() and provide both the request/computation and a separate promise that rejects the promise after 5 seconds. If you don’t get the expected result you know that the task didn’t resolve in a timely fashion:

loops and async/await

Remember how we’ve talked about Promise.all() and how it doesn’t respect the order of execution? If that’s important for your use case for...of loop is a great way to go. Let’s look at an example:

It’s really similar to what we’ve looked at previously. We’re iterating over three promises, awaiting for the result and logging the message out. You could also use a basic for loop, but I like for…of more because it looks a bit cleaner.

We can also leverage map to create an array of promises and use Promise.all() to deal with. It’s also possible to manipulate the return data if you want!

Here we’re creating promises on the fly with a helper function createPromise , mapping them into an array called greetingPromises and awaiting inside to manipulate the data from the promise. This is really cool and we could use this technique in a variety of scenarios, for example processing responses of multiple requests to the server.

One thing that I should mention is that I don’t recommend using async/await with forEach, as it’s not promise-aware, it won’t wait for execution to end and will just jump unto the next iteration.

As you can see start and finish messages are logged out first, even though we’re waiting for inside the forEach loop for the message.

Thanks for reading, I hope you’ve learnt something new!

--

--