Skip to content

Use Promise.all to Wait for Multiple Promises in Node.js

Sometimes in Node, we need to execute multiple asynchronous operations concurrently, wait for them all to complete, and then do something with the combined result. An example of this would be to wait for multiple API calls to finish before gathering results and using them to form another API call. To accomplish this, we can use JavaScript’s Promise.all() to wait for multiple Promises to resolve, before doing something with their results. This allows us to write code in Node that’s easier to maintain and understand.

In this tutorial we’ll:

  • Learn how to use JavaScript’s Promise.all to wait for multiple Promises to return
  • Practice with a use case for Promise.all using NPM node-fetch

By the end of this tutorial you should be able to use Promise.all to simultaneously execute multiple asynchronous operations.

Goal

Use Promise.all to make multiple concurrent fetch requests and then act on the data only once they have all resolved.

Prerequisites

Watch

What is Promise.all?

Promise.all is a utility that takes an iterable (most commonly an array) of Promises as input, and returns a single Promise that resolves once all the Promises have resolved, and rejects with the error from the first Promise that rejects.

Using Promise.all, we can make multiple API calls, create a Promise that resolves when all the calls have finished, and return the results in an array. This lets us create a “basket” of Promises, and handle their return values all at once.

If any of the Promises passed to Promise.all reject, Promise.all rejects with the value of the Promise that rejected, whether or not the other Promises have resolved. This is considered “fail fast” behavior; as soon as one Promise rejects, the Promise returned by Promise.all will immediately reject as well.

How to use Promise.all

First we need an array of Promises to pass to Promise.all. Next, we can add a then handler to the returned Promise, which will receive the result of the resolved Promises. To be safe, we attach a catch handler so any potential errors can be handled.

Here, we create a single new Promise that resolves after all of these timeouts wrapped in Promises complete:

const all = Promise.all([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => resolve(2), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).catch(err => console.log("Promise was rejected!", err));
all.then(results => console.log(results)); // [1, 2, 3]

That new Promise, called all above, is like a “basket”, into which we have stuffed an array of other Promises. It will wait until all the Promises have been resolved and bundle their resolved results together into an array which is passed to the then handler. The resolved values in the results array will be in the same order as the Promises were.

Note that all 3 inner Promises are started at the same time. Instead of taking 6 total seconds, our new Promise should resolve after about 3 seconds, since it will resolve once all timers have completed, and the longest timeout is 3 seconds.

Promise.all is most useful when you have multiple Promises, and want to know when they have all completed their operations before doing something with their results.

Example with fetch

Here is a more real-world use case, requesting info from an API based on an array of ids. To run this example yourself, you need to install node-fetch from npm.

const fetch = require("node-fetch");
const url = "https://jsonplaceholder.typicode.com/todos";
const ids = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// fetch returns a Promise.
const getTodo = id => fetch(`${url}/${id}`)
.then(response => response.json());
// Map over our ids, returning a promise for each one.
const arrayOfPromises = ids.map(id => getTodo(id));
Promise.all(arrayOfPromises)
.then(todos => todos.map(todo => todo.title))
.then(titles => console.log(titles)) // logs titles from all the todos
.catch(err => console.log(err));

The function getTodo takes an id and returns a Promise. We map over the array of ids to create a new array of Promises, which we then pass to Promise.all. That executes each Promise and waits for them all to resolve.

If one of the API calls throws an error or the Promise otherwise rejects, Promise.all is rejected immediately and .catch is called instead of .then.

This is much cleaner than nesting the calls one after another in callbacks, or chaining them together with .then handlers. Additionally, with Promise.all your Promises will run concurrently, not sequentially like they would if chained.

Promise.all saves us from writing error-prone and messy code with either callback nesting or unnecessary Promise chains by allowing us to compose a new Promise.

Fault-tolerant Promise.all

Let’s say you’re using Promise.all on 10 API calls, and 1 of them throws an error. Normally, the other Promises will be halted, and you won’t get the results from any of them. To still receive results from Promise.all in a case where some Promises reject, we need to make Promise.all fault tolerant.

To avoid losing the other request responses, we can attach catch handlers to the individual Promises passed to Promise.all. This way we are catching the errors they might throw, instead of letting them bubble up to the Promise.all which will cause the Promise to reject.

const promises = [
fetch(url)
fetch(url)
fetch(url)
Promise.reject(new Error('This fails!'))
]
Promise.all(promises.map(promise => promise.catch(error => error)))
.then(results => {
// we get results even if a promise rejected!
// results can be a mix of errors and success values; you'd have to sort them yourself
})

This is a handy trick if you need to access the data from the requests regardless of whether any fail.

Recap

When you need to make several asynchronous calls and want to gather their results together, use Promise.all. Promise.all lets us say, “Here’s a collection of Promises, let me know when they’ve all completed, or as soon as one goes wrong, and I’ll handle it from there.” This allows us to write code that’s easier to maintain and understand.

Further your understanding

  • Give some examples of when Promise.all might help speed up your application.
  • Can you think of any way to salvage the results when 1 of the 10 API calls fails?

Additional resources