Promise.all() and its polyfill

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

image.png

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

image.png 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()

  1. 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().
  2. When numerous async operations do not need to be coordinated.
  3. 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().
  4. 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().