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.resolvereturns a value; -
Promise.rejectrejects an error; -
Promise.allwaits until list of promises is resolved or rejected; -
Promise.racewaits 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
anyusingallis very neat, as it's an application of De Morgan's Laws.This is fantastic. Thanks!
Glad to hear that!