Promise.any
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; });
}));
};
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 toPromise.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 rejectp
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.
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
orbluebird
before being added to the spec. There are lots of useful methods on Promise which I would add beforePromise.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.)
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(); });
}));
};
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 toPromise.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 rejectp
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.
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
)
Jake Archibald recently pointed out that the promise returned by
Promise.race
is rejected if any of the promises passed toPromise.race
get rejected before one was resolved. So in the following example: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 rejectp
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.
Would TC39 be willing to (re)consider adding something like this to the spec?