DEV Community

Sergio Tskhovrebov
Sergio Tskhovrebov

Posted on • Edited on

Looking for Promise.any? Let's quickly implement a polyfill for it.

The Problem

As we all know, we often write asynchronous code using Promise object, which is available since ES6 (ECMAScript 2015). It gracefully offers us several methods.

  • Promise.resolve returns a value;
  • Promise.reject rejects an error;
  • Promise.all waits until list of promises is resolved or rejected;
  • Promise.race waits until any of the promises is resolved or rejected.

There is also Promise.any method (more details), that could be very helpful for us. It returns the first resolved promise and stops execution, ignoring the other promises. It is an ECMAScript language proposal and is not supported by browsers yet.

The Solution

Fortunately, we can implement such behaviour ourselves:

const promiseAny = async <T>(
  iterable: Iterable<T | PromiseLike<T>>
): Promise<T> => {
  return Promise.all(
    [...iterable].map(promise => {
      return new Promise((resolve, reject) =>
        Promise.resolve(promise).then(reject, resolve)
      );
    })
  ).then(
    errors => Promise.reject(errors),
    value => Promise.resolve<T>(value)
  );
};
Enter fullscreen mode Exit fullscreen mode

Some details

Let's dive deeper into the process.

The main idea is transforming the passed promises list into a reverted promises list. When a reverted Promise resolves it calls reject, while when it rejects it calls resolve. Then the reverted promises list is passed to Promise.all method and when any of Promises rejects, Promise.all will terminate execution with reject error.
However in reality this means that we have the successful result, so we just transform the result from reject to resolve back and that's all.
We got first successfully resolved promise as a result without magic wand.

More details

As a parameter we can pass an Array containing Promises or basic data types (number, String, etc.). To handle basic types we have to promisify them using Promise.resolve(promise).
PromiseLike is built-in TypeScript data type that wraps and properly handles promises from different libraries that you can use (such as jQuery, bluebird, Promises/A+, etc.)

Another interesting point is the Iterable type. It's usage means that we can pass in our function not only an Array but also a Map, a Set or even a Generator Function, that's to say any object implementing Iterable protocol. Our polyfill handles that argument type out of the box using [...iterable].map command.

Top comments (3)

Collapse
 
gypsydave5 profile image
David Wickes • Edited

in the interests of total obfuscation:

const promiseAny = async iterable => Promise.all(
  [...iterable].map(promise => 
    new Promise((resolve, reject) => promise.then(reject, resolve))
  )
).then(Promise.reject.bind(Promise), Promise.resolve.bind(Promise));
Enter fullscreen mode Exit fullscreen mode

Implementing any using all is very neat, as it's an application of De Morgan's Laws.

Collapse
 
alistaiiiir profile image
alistair smith

This is fantastic. Thanks!

Collapse
 
sinxwal profile image
Sergio Tskhovrebov

Glad to hear that!