Promise.any

# Tobie Langel (3 years ago)

Jake Archibald recently pointed out that the promise returned by Promise.race is rejected if any of the promises passed to Promise.race get rejected before one was resolved. So in the following example:

var p = Promise.race([
    new Promise(function(_, reject) { reject(new Error("boom!")); }),
    new Promise(function(resolve) { setTimeout(resolve, 1000, "foo"); })
]);

p will be rejected with error "boom!" (instead of being resolved with value "foo").

Although this seems to fit some use case, it doesn't work when the aim is to resolve p with whichever promise resolves first and only reject p when all promises have failed. For example, when doing a cache lookup and racing it against the network, you want to display the data from whichever source resolves first, and only display a fallback resource when neither the cache nor the network are available.

In the same thread, Tab Atkins mentioned Promise.any fulfills this use case and was briefly discussed within TC39 before being abandoned.

I'd imagine a pseudo implementation would look something like the below, though you might want to surface all of the errors somehow.

Promise.any = function(promises) {
    return new Promise(function(resolve, reject) {
        var count = promises.length, resolved = false;
        promises.forEach(function(p) {
            Promise.resolve(p).then(function(value) {
                resolved = true;
                count--;
                resolve(value);
            }, function() {
                count--;
                if (count === 0 && !resolved) {
                    reject(new Error("No promises resolved successfully."))
                }
            });
        });
    });
};

Would TC39 be willing to (re)consider adding something like this to the spec?

# C. Scott Ananian (3 years ago)

I would think that proposed additions to the Promise spec ought to be prototyped in something like prfun or bluebird before being added to the spec. There are lots of useful methods on Promise which I would add before Promise.any.

Here's a very short implementation of Promise.any:

Promise.any = function(promises) {
  var errors = [];
  return Promise.race(promises.map(function(p) {
     return p.catch(function(e) { errors.push(e); if (errors.length >=

promises.length) throw errors; });
  }));
};
# Calvin Metcalf (3 years ago)
# Tab Atkins Jr. (3 years ago)

On Thu, Jul 17, 2014 at 9:28 AM, Tobie Langel <tobie.langel at gmail.com> wrote:

Hi all,

[Jake Archibald recently pointed out][1] that the promise returned by Promise.race is rejected if any of the promises passed to Promise.race get rejected before one was resolved. So in the following example:

var p = Promise.race([
    new Promise(function(_, reject) { reject(new Error("boom!")); }),
    new Promise(function(resolve) { setTimeout(resolve, 1000, "foo"); })
]);

p will be rejected with error "boom!" (instead of being resolved with value "foo").

Although this seems to fit some use case, it doesn't work when the aim is to resolve p with whichever promise resolves first and only reject p when all promises have failed. For example, when doing a cache lookup and racing it against the network, you want to display the data from whichever source resolves first, and only display a fallback resource when neither the cache nor the network are available.

In the same thread, [Tab Atkins mentioned][2] Promise.any fulfills this use case and was briefly discussed within TC39 before being abandoned.

I think we should, as the use-case seems valuable and is tricky to put together manually.

I think the errors should be surfaced; we might define a CompositeError exception class and have it contain an array of the errors. (This is necessary to maintain the practical invariant that UA-rejected promises are alwyas rejected with errors.)

I initially thought that the name might not be too clear, but after some consideration, I think .any() is fine. We're trying to distinguish between "race to settle" and "race to fulfill" here, with the former already named race(). Luckily, "any" is similar to "all", both in naming and behavior, as "all" means "all have fulfilled", so "any" meaning "any have fulfilled" works well.

# Tab Atkins Jr. (3 years ago)

On Thu, Jul 17, 2014 at 9:53 AM, C. Scott Ananian <ecmascript at cscott.net> wrote:

I would think that proposed additions to the Promise spec ought to be prototyped in something like prfun or bluebird before being added to the spec. There are lots of useful methods on Promise which I would add before Promise.any.

Here's a very short implementation of Promise.any:

Promise.any = function(promises) {
  var errors = [];
  return Promise.race(promises.map(function(p) {
     return p.catch(function(e) { errors.push(e); if (errors.length >=
promises.length) throw errors; });
  }));
};

Nope, that'll accept with undefined if the first settled promise is rejected. ^_^ This is non-trivial to do correctly; you need to have rejected promises (except the last) resolve to an ever-pending promise instead. (I think that's correct.)

# C. Scott Ananian (3 years ago)

On Thu, Jul 17, 2014 at 1:41 PM, Tab Atkins Jr. <jackalmage at gmail.com> wrote:

Nope, that'll accept with undefined if the first settled promise is rejected. ^_^ This is non-trivial to do correctly; you need to have

Fixed:

Promise.any = function(promises) {
   var errors = [];
   return Promise.race(promises.map(function(p) {
      return p.catch(function(e) { errors.push(e); if (errors.length >=
 promises.length) throw errors; return Promise.race(); });
   }));
};
# Mark S. Miller (3 years ago)

On Thu, Jul 17, 2014 at 9:28 AM, Tobie Langel <tobie.langel at gmail.com>

wrote:

Hi all,

[Jake Archibald recently pointed out][1] that the promise returned by Promise.race is rejected if any of the promises passed to Promise.race get rejected before one was resolved. So in the following example:

var p = Promise.race([
    new Promise(function(_, reject) { reject(new Error("boom!")); }),
    new Promise(function(resolve) { setTimeout(resolve, 1000, "foo"); })
]);

p will be rejected with error "boom!" (instead of being resolved with value "foo").

Although this seems to fit some use case, it doesn't work when the aim is to resolve p with whichever promise resolves first and only reject p when all promises have failed. For example, when doing a cache lookup and racing it against the network, you want to display the data from whichever source resolves first, and only display a fallback resource when neither the cache nor the network are available.

In the same thread, [Tab Atkins mentioned][2] Promise.any fulfills this use case and was briefly discussed within TC39 before being abandoned.

I'd imagine a pseudo implementation would look something like the below, though you might want to surface all of the errors somehow.

Promise.any = function(promises) {
    return new Promise(function(resolve, reject) {
        var count = promises.length, resolved = false;
        promises.forEach(function(p) {
            Promise.resolve(p).then(function(value) {
                resolved = true;
                count--;
                resolve(value);
            }, function() {
                count--;
                if (count === 0 && !resolved) {
                    reject(new Error("No promises resolved successfully."))
                }
            });
        });
    });
};

Would TC39 be willing to (re)consider adding something like this to the spec?

For ES7, sure, this is something we can consider. ES6 promises intentionally have a small API surface -- a much smaller one than I expect for ES7 promises. As for what should be added, that will of course be an extended discussion. Happy to see it start here.

# C. Scott Ananian (3 years ago)

Note that the original use case relies on the "cache" throwing an exception if the entry is not found. If the cache instead returns Promise.race() (that is, a never-fulfilled promise) for entry-not-found, then Promise.race(tryCache(), tryNetwork()) just works.

I'm not fond of the rejection handling of the bluebird utility functions Promise.some() and Promise.any(). I considered them for prfun and decided that I didn't want to support this API.

Note also that the matter of merging/reporting all the rejections is awkward; bluebird uses a new AggregateError class for this. And as was mentioned earlier, there's no good way to get the set of all rejections is the result eventually resolves.

But I would support some variant on bluebird's Promise.some() that generalized Promise.race to a specific number of fulfilled promises. I think preserving the "reject if any reject" behavior of Promise.race is important, but an ES7 Promise.some(n) could return an array of length n with the first n promises to be fulfilled. --scott (who goes off to try implementing that in prfun)