Promise.any() implementation

What is Promise.any()

Promise.any() is a static promise concurrency method that takes an iterable of elements (usually Promises) as an input, and returns a single Promise.

This returned promise will resolve when any of the input's promises fulfills with the first fulfillment value. It rejects when all of the input's promises rejects (including empty iterable is passed) with an AggregateError containing an array of rejection reasons

Usage

Promise.any() method is typically used when we need to return the first promise that fulfills. It short-circuits after a promise fulfills, so it does not wait for the other promises to complete once it finds one.

This can be beneficial if we need only one promise to fulfill but we do not care which one does

example -

const p1 = Promise.resolve(2);
const p2 = 5;
const p3 = new Promise((resolve,reject) => {
  setTimeout(() => resolve("response after 2s"), 2000)
})
 
 
const result = await Promise.promiseAny([p1,p2, p3]); 
// output - 
// Promise fulfilled with value: 2

Implementation of our own promiseAny method -

  • We'll maintain an array items to hold rejected response of each promise and a variable unresolved to keep a count of promises that are unresolved till the point.

  • if any one of our promises is settled/fulfilled then we'll return that promise that resolves with fulfill value of that promise.

  • when no promise is fulfilled or iterable is empty we will return a rejected promise that rejects with AggregateError that contains an array of rejection reason for each iterable in the same order as inputted promises.

Note

The returned promises should contain the resolved values in the same order as inputted promises.

Edge cases -

  • If an empty iterable is passed, then the promise returned by this method is rejected synchronously. The rejected reason is an AggregateError object whose errors property is an empty array.

implementation of promiseAny function -

function promiseAny(iterable) {
  return new Promise((resolve,reject) => {
    const items = new Array(iterable.length);
    let unresolved = iterable.length;
 
    if(!unresolved) {
      reject(new AggregateError([]));
    }
 
    iterable.forEach((p, idx) => {
      Promise.resolve(p)
      .then(res => {
        resolve(res);
      })
      .catch(err => {
        items[idx] = err;
        unresolved -= 1;
        if(unresolved === 0) {
          reject(new AggregateError(items, "All promises were rejected"));
        }
      })
    })
  })
}

Usage example of our promiseAny function

  • using promiseAny with all resolved promises
const p1 = Promise.resolve(2);
const p2 = 5;
const p3 = new Promise((resolve,reject) => {
  setTimeout(() => resolve("response after 2s"), 2000)
})
 
const result = await promiseAny([p1, p2, p3]); 
// output - 
// Promise fulfilled with value: 2
  • using promiseAny with rejected promise
const p1 = new Promise((res, rej) => reject(3));
const p2 = new Promise((resolve,reject) => {
  setTimeout(() => reject("api call failed"), 2000);
});
 
try {
await promiseAny([p1, p2]);
} catch (err) {
  console.log(e instanceof AggregateError); // true
  console.log(e.errors); // [ 2, "api call failed" ]
}

Info

You can read more about Promise.any() on mdn docs here