Q22: Implement a polyfill for Promise.any().

Difficulty:AdvancedTopic:Promises

Answer

Promise.any() fulfills as soon as any input fulfills. If every input rejects, it rejects with an AggregateError containing the rejection reasons.

if (!Promise.myAny) {
  Promise.myAny = function (iterable) {
    return new Promise((resolve, reject) => {
      let items;

      try {
        items = Array.from(iterable);
      } catch (error) {
        reject(error);
        return;
      }

      const errors = new Array(items.length);
      let remaining = items.length;

      if (remaining === 0) {
        reject(new AggregateError([], "All promises were rejected"));
        return;
      }

      items.forEach((item, index) => {
        Promise.resolve(item).then(
          resolve,
          error => {
            errors[index] = error;
            remaining--;

            if (remaining === 0) {
              reject(new AggregateError(errors, "All promises were rejected"));
            }
          }
        );
      });
    });
  };
}

Promise.myAny([
  Promise.reject("error 1"),
  Promise.resolve("success"),
  Promise.reject("error 2")
]).then(console.log);
// success

Explanation

The first fulfilled input resolves the aggregate promise. Rejections are recorded by input index. Only after every input rejects does the method reject with an AggregateError.

An empty iterable rejects with an AggregateError whose errors collection is empty.

Key Takeaways
  • Promise.any succeeds when the first input fulfills.
  • It rejects with AggregateError only when every input rejects or when the input is empty.