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)
);
};
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)
in the interests of total obfuscation:
Implementing
any
usingall
is very neat, as it's an application of De Morgan's Laws.This is fantastic. Thanks!
Glad to hear that!