3 ways to implement (polyfill) Promise.all

How to implement Promise.all is very often asked as an interview question to get a sense of how familiar is the interviewee with `Promises` and asynchronous code, but even if you are not currently interviewing this is something that you should know as a Javascript Developer.

Promise.all is a static method on the Promise object that takes a list of items and returns a promise that resolves with a list containing the values of all resolved values in the input list. If any of the values are rejected promises, the returned promise will also be rejected with the rejection message of the promise that is rejected first. This is particularly helpful for when you want to run multiple promises concurrently, but wait until all of them have been fulfilled before continuing.

If you’re using promises directly in your code you may write something like this to make multiple concurrent requests to different API endpoints, and wait until all have completed to operate on the responses.

Promise.all([
    fetch('/api/a'),
    fetch('/api/b'),
    fetch('/api/c')
]).then([responseA, responseB, responseC] => {
    // Use the responses from all three async requests.
});

The code above looks pretty straigthforward but do you really know what is happening under the hood?

This is not doing anything special or tricky, it's just iterating over the promises that we are passing, and rejecting or fulfilling them, after that we return the results which could be a error or an actual value in a array form, but how this looks in code?

Recursive Solution

Promise.all can be implemented using a recursive solution. The base case is when Promise.all is called with an empty array, in which case it returns a promise that resolves to an empty array. Otherwise it take the resolved value of the first item in the list and calls Promise.all on the rest of the elements in the list.

Promise.all = function promiseAllRecursive(values) {
    // Base case.
    if (values.length === 0) {
        return Promise.resolve([]);
    }
    
    const [first, ...rest] = values;
    
    // Calling Promise.resolve on the first value because it could
    // be either a Promise or an actual value.
    return Promise.resolve(first).then(firstResult => {
        return promiseAllRecursive(rest).then(restResults => {
            return [firstResult, ...restResults];
        });
    });
}

Iterative Solution

For the iterative solution, you’ll want to return a new promise that only resolves once each of the values of the array provided has resolved, and rejects if any of the promises reject.

The executor function given to your function can keep track of the results as each promise resolves and keep track of the number of promises that have resolved. You can use a for loop or a forEach to iterate over the list of values and call the thenmethod on each of them, adding the result to the results list as they resolve. It’s important to remember that Promise.all maintains the order of the results from the promises provided as input, so you can’t just append to the results list whenever a promise resolves. You’ll need to know the index of the promise that is resolving in order to know where to place it in the results list. In the example I’m doing this by taking the index argument to the forEach callback.

Promise.all = function promiseAllIterative(values) {
    return new Promise((resolve, reject) => {
       let results = [];
       let completed = 0;
       
       values.forEach((value, index) => {
            Promise.resolve(value).then(result => {
                results[index] = result;
                completed += 1;
                
                if (completed == values.length) {
                    resolve(results);
                }
            }).catch(err => reject(err));
       });
    });
}

Reducer Solution

Yet another way to implement Promise.all is to use a reduce function. The initial value for the reduce function will be a Promise that resolves to an empty list, in a similar fashion to the base case to the recursive solution. Our reducer function will take an accumulator, which will be a promise that will resolve to all of the results of the resolved values so far, and a value argument, which is the current value in the iteration on the list of values (promise or not) to Promise.all. The reducer function should return a new promise that will resolve to the list of results that the accumulator will resolve to, as well as the result that the current value will resolve to. As the reducer iterates over the list of values, each return value will be a promise that resolves to a larger subset of the results of the values passed to Promise.all.

We don’t need to explicitly handle catching promise rejecting because the promise we return will be implicitly rejected.

Promise.all = function promiseAllReduce(values) {
    return values.reduce((accumulator, value) => {
        return accumulator.then(results => {
            return Promise.resolve(value).then(result => {
                return [...results, result];
            });
        });
    }, Promise.resolve([]));
}

There you go, 3 different ways to implement Promise.all to maybe show off at your next Javascript Interview.

If you enjoyed this post don't forget to subscribe to our newsletter, I promise you only send relevant and interesting content :)