JavaScript Promise.all
Learn about the JavaScript Promise.all method including when to use Promise.all, Promise.all fulfillment, Promise.all rejection, and Promise.all order.
Table of Contents 📖
- Promise.all()
- Promise.all() Fulfillment
- Promise.all() Fulfilled Order
- Promise.all() Rejection
- Promise.all() Reject Order
- Promise.all() Use Case
Promise.all()
Promise.all is a static method of the Promise class that executes multiple asynchronous tasks concurrently. However, it should be noted that as JavaScript is single threaded only one task will be executing at a time. Nevertheless, control can shift between different asynchronous tasks which makes the execution of the tasks provided to Promise.all seem concurrent.
Promise.all() Fulfillment
The JavaScript Promise.all method takes an iterable of promises as input, such as an arry of promises, and returns a single promise when all of the promises in the iterable are fulfilled. For a demonstration, lets create multiple promises. Each of these promises will be fulfilled after 2 seconds (2000 milliseconds).
const promise1 = new Promise((resolve, reject) => { setTimeout(resolve, 2000, 'promise1'); }); const promise2 = new Promise((resolve, reject) => { setTimeout(resolve, 2000, 'promise2'); }); const promise3 = new Promise((resolve, reject) => { setTimeout(resolve, 2000, 'promise3'); });
After 2 seconds, each promise will be fulfilled and the values promise1, promise2, and promise3 will be passed to the resolve functions respectively. Now, lets pass each one of these promises to the Promise.all method and attach a then to it.
Promise.all([promise1, promise2, promise3]).then((values) => { console.log(values); // [ 'promise1', 'promise2', 'promise3' ] });
After 2 seconds have passed, all of the promises passed to Promise.all have resolved. As a result, the Promise.all promise is resolved. When the Promise.all promise is resolved, its value consists of an array of the fulfillment values from the promises provided to it.
Promise.all() Fulfilled Order
The fulfillment values are listed in the order they were passed in the iterable, not the completion order. For example, if we change promise1 to resolve after 5 seconds as opposed to 2, it will resolve before promise2 and promise3. However, it was passed first to Promise.all so it is still listed first.
const promise1 = new Promise((resolve, reject) => { setTimeout(resolve, 5000, 'promise1'); // Now resolves in 5 seconds }); const promise2 = new Promise((resolve, reject) => { setTimeout(resolve, 2000, 'promise2'); }); const promise3 = new Promise((resolve, reject) => { setTimeout(resolve, 2000, 'promise3'); });
Promise.all([promise1, promise2, promise3]).then((values) => { console.log(values); // [ 'promise1', 'promise2', 'promise3' ] promise1 still listed first });
Promise.all() Rejection
So the promise returned from Promise.all is fulfilled when all the input's promises have fulfilled. However, if any of the promises provided are rejected, Promise.all fails instantly and returns the reason for the failure. This is known as fail-fast behavior. If one of the promises rejects, Promise.all rejects immediately. For a demonstration, lets reject promise2 after 2 seconds. Also, add a catch block to the Promise.all chain and log out the error.
const promise1 = new Promise((resolve, reject) => { setTimeout(resolve, 5000, 'promise1'); // Resolved after 5 seconds }); const promise2 = new Promise((resolve, reject) => { setTimeout(reject, 2000, 'promise2 was rejected!'); // Rejected after 2 seconds }); const promise3 = new Promise((resolve, reject) => { setTimeout(resolve, 2000, 'promise3'); });
Promise.all([promise1, promise2, promise3]).then((values) => { console.log(values); }).catch((err) => { console.log(err); // promise2 was rejected! });
So, promise1 and promise3 were still resolved but promise2 was rejected. As such, the promise returned from Promise.all is rejected as it is rejected when any of the input's promises are rejected. Also, Promise.all was rejected as soon as promise2 failed.
Promise.all() Reject Order
When it comes to order and Promise.all rejection, the promise returned from Promise.all is rejected with the first rejection reason. For example, lets make promise1 reject after 5 seconds and keep promise2 at 2 seconds. The error message provided in the catch block will still be from promise2. This is because of its fail-fast behavior.
const promise1 = new Promise((resolve, reject) => { setTimeout(reject, 5000, 'promise1 was rejected!'); // Rejected after 5 seconds }); const promise2 = new Promise((resolve, reject) => { setTimeout(reject, 2000, 'promise2 was rejected!'); // Rejected after 2 seconds }); const promise3 = new Promise((resolve, reject) => { setTimeout(resolve, 2000, 'promise3'); });
Promise.all([promise1, promise2, promise3]).then((values) => { console.log(values); }).catch((err) => { console.log(err); // promise2 was rejected! });
If we changed promise1 to be rejected after 1 second, then the error message would be promise1 was rejected!
Promise.all() Use Case
The Promise.all method is typically used for working with multiple related asynchronous tasks that all need to be fulfilled before continuing code execution. For example, say we need to get data from multiple APIs and then combine them, Promise.all would be a great solution.
async function getWittCodeFavNum() { const response = await fetch('/wittcode'); return await response.json(); }
async function getGregFavNum() { const response = await fetch('/greg'); return await response.json(); }
Promise.all([
getWittCodeFavNum(),
getGregFavNum(),
]).then((values) => {
const [wittCodeNum, gregNum] = values;
const sum = wittCodeNum + gregNum;
console.log(The sum of everyone's fav numbers is: ${sum}.
);
}).catch((err) => {
console.log(err);
});
Here, we can make calls to /wittcode and /greg concurrently with Promise.all. Promise.all is also used with over-awaiting code in an asynchronous function. For example, it wouldn't be uncommon to write the following above in an async function using two await calls.
async function getFavNums() {
const wittCodeNum = await getWittCodeFavNum();
const gregNum = await getGregFavNum();
const sum = wittCodeNum + gregNum;
console.log(The sum of everyone's fav numbers is: ${sum}.
);
}
Here, the call to /greg will not be made until the call to /wittcode has completed. However, these two calls do not rely on one another so writing it this way would be inefficient. We can make this more efficient with Promise.all like so.
async function getFavNums() {
const [wittCodeNum, gregNum] = await Promise.all([
getWittCodeFavNum(), getGregFavNum()
]);
const sum = wittCodeNum + gregNum;
console.log(The sum of everyone's fav numbers is: ${sum}.
);
}
Here, the call to /greg will be made concurrently with the call to /wittcode which is more efficient. Promise.all is also a good idea here because the final result relies on both promises. In other words, the sum of the favorite numbers relies on the result from both /wittcode and /greg so if either fail, the promise is rejected instantly, then error handling is invoked.