Promise.all() and its polyfill
Part 1 of the "Promise methods and their polyfills" series.
So, you finally know what promises are and how they freed us from the clutches of "callback hell", but that's not the end. Several different promise methods are used in the industry which are useful and powerful.
This blog will introduce you to the method Promise.all()
and help you write its polyfill function.
Preface
It is assumed that you are comfortable with javascript and have knowledge about promises.
What is Promise.all()
const promiseAll = Promise.all([promise1, promise2, promise3...])
Promise.all()
takes an array of promises (values too) as input and it returns a single Promise
.
It will run all the promises until one of the following conditions is met
- All of the promises in the array resolve, which would resolve
promiseAll
with an array containing fulfilled values of individual promises. - One of the promises in the input array rejects, which would reject
promiseAll
immediately and throw an error/rejection message.
Promise.all() in action
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Promise 1 resolved");
}, 1000);
});
const promise2 = Promise.resolve("Promise 2 resolved");
const promise3 = 8;
const promiseAll = Promise.all([promise1, promise2, promise3]);
promiseAll.then((result) => {
console.log(result);
});
Output
As you can see we get an array of fulfilled values of the individual promises. Also, the order of the received values stays the same.
When one of the promise rejects
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Promise 1 resolved");
}, 1000);
});
// instead of resolving, we reject promise 2
const promise2 = Promise.reject("Promise 2 reject");
const promise3 = 8;
const promiseAll = Promise.all([promise1, promise2, promise3]);
promiseAll.then((result) => {
console.log(result);
});
Output
promiseAll
is immediately rejected when promise2
rejects and thus we get the rejection/error message.
Our implementation/polyfill of Promise.all()
We want our polyfill to behave the same way as Promise.all()
does.
const promiseAll = myPromiseAll([promise1, promise2, promise3]);
promiseAll.then((result) => {
console.log(result); // ['promise 1 resolved','promise 2 resolved',8]
});
- It should return a promise
- If all promises are resolved successfully, then allPromise fulfills with an array containing fulfilled values of individual promises.
- If a promise rejects, we should reject our promise.
myPromiseAll() function
function myPromiseAll(promisesArr) {
//to store results
const results = [];
// to keep track of resolved promises
let completed = 0;
return new Promise((resolve, reject) => {
// loop over promises array
promisesArr.forEach((value, index) => {
// instead of doing value.then(), we have to use
// Promise.resolve(value) so that we can convert
// a non "promise" input into a "promise", and later
// chain it with .then()
Promise.resolve(value)
.then((res) => {
// we will store our resolved value in results array
// and maintain the order of resolved values
results[index] = res;
completed++; // increase the completed counter
// when "completed" is equal to the length of the received array
// it means that all the values of the array are resolved
// and we should resolve our whole promise
if (completed === promisesArr.length) {
resolve(results);
}
})
// if a single promise from the array is rejected
// we should reject our whole promise
.catch((err) => reject(err));
});
});
}
The complete code
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Promise 1 resolved");
}, 1000);
});
const promise2 = Promise.resolve("Promise 2 resolved");
const promise3 = 8;
function myPromiseAll(promisesArr) {
const results = [];
let completed = 0;
return new Promise((resolve, reject) => {
promisesArr.forEach((value, index) => {
Promise.resolve(value)
.then((res) => {
results[index] = res;
completed++;
if (completed === promisesArr.length) {
resolve(results);
}
})
.catch((err) => reject(err));
});
});
}
const promiseAll = myPromiseAll([promise1, promise2, promise3]);
promiseAll.then((result) => {
console.log(result); // ['promise 1 resolved','promise 2 resolved',8]
});
Use case of Promise.all()
Promise.all()
is useful when you want to coordinate among multiple async operations and run some further code using all the results received.
It is most commonly used to make multiple API calls and do something further when you receive the results.
Promise.all([apiRequest(...), apiRequest(...), apiRequest(...)]).then(function(results) {
// API results in the results array here
// processing can continue using the results of all three API requests
}, function(err) {
// an error occurred, process the error here
});
When NOT to use Promise.all()
- When there is only one async action. With only one action, you may simply use a .then() handler on the single promise, eliminating the need for Promise.all().
- When numerous async operations do not need to be coordinated.
- When a fast fail implementation is not suitable. If you require all results, even if some fail, Promise.all() will not enough. Instead, you should use something like Promise.allSettled().
- If your async operations do not all return promises, Promise.all() cannot track an async operation that is not managed through a promise.
Conclusion
Initially, this may seem daunting but it's quite easy once you run the code yourself. Reach out to me on Twitter if you face any difficulties!
In part 2 of this series, I'll explain Promise.allSettled() which fills in the gap left by Promise.all().