Promise.pick: Predicate-Based Promise Selection

I would like to propose a new Promise combinator: Promise.pick(iterable, selector).

Promise.pick resolves with the first fulfilled value that satisfies a user-defined predicate. Unlike existing combinators such as Promise.all, Promise.race, Promise.any, and Promise.allSettled, which select based on timing or structural completion, this proposal introduces predicate-based selection semantics. The intent is to express logical suitability directly rather than relying on settlement order.

A common requirement is to obtain the first acceptable result among multiple concurrent asynchronous operations. Current patterns typically require composing existing combinators manually, for example using Promise.allSettled() followed by filtering, or transforming inputs before passing them to Promise.any(). These approaches either wait for all promises to settle, modify rejection behavior, or obscure the intent of predicate-based selection.

The proposed API is:

Promise.pick(iterable, selector)

Where iterable is an iterable of values or Promises, and selector is a predicate applied to fulfilled values.

Example:

Promise.pick(
  [fetchPrimary(), fetchBackup()],
  response => response.status === 200
);

This resolves with the first fulfilled response whose status is 200, independent of settlement order.

See it here

I agree this is a thing which is reasonable to want, but I think it’s more naturally expressed as a combination of two things rather than its own primitive.

First is converting a synchronous iterable of promises to an AsyncIterator by racing, so you can consume all values in the order in which they settle, rather than (like with Promise.race) only getting the first one.

Second is AsyncIterator.prototype.find, which works just like an async version Array.prototype.find except it works on async iterators.

In combination this would let you do

AsyncIterator.race([fetchPrimary(), fetchBackup()]).find(response => response.status === 200)

which I think is clear, concise, and correct.

7 Likes

Thank you for the suggestion. The AsyncIterator.race(...).find(...) composition is indeed expressive and can model similar behavior. The motivation behind Promise.pick was to provide a Promise-level primitive that expresses predicate-based fulfillment selection directly, in symmetry with existing combinators like all, race, and any.

One question is whether this pattern should require moving into the async iterator abstraction, or whether there is value in keeping such selection semantics within the Promise API itself. I’m interested in thoughts on that abstraction boundary.