PerformPromiseAll
Promise.resolve
doesn't use the species pattern any more:
esdiscuss.org/topic/fixing-promise-resolve
The rationale was that resolve
is more like a constructor than a mutator.
I don't have a strong opinion about Promise.all
, but I think perhaps it
would be better if none of the static methods of Promise
(Promise.all,
Promise.race, Promise.reject) used the species pattern, which is more
appropriate for instance methods (like Promise.prototype.then
, where it
is a natural fit).
However, consider another way of looking at it. Let's make the fundamental
implementation Promise.prototype.all
(defined by some utility libraries,
including bluebird and prfun, and would be a good candidate for ES7) which
operates on a promise resolving to an array. It seems natural for that to
use a species pattern, since it's an instance transformation method, like
then
and catch
. The natural implementation of Promise.all
would then
be:
Promise.all = function(a) { return this.resolve(a).all(); };
This doesn't use the species pattern for the initial resolve
, but then
does use it to implement the instance method all
. So the result of
Promise.all
effectively uses the species pattern.
So: on one hand, static methods don't use @species. On the other,
Promise.prototype.all
would naturally use the species pattern, and that
suggests a natural implementation of Promise.all
would as well. On the
gripping hand, given a non-species-using Promise.all
, it is easy to
define Promise.prototype.all
(just do the initial resolve
using the
species), but the converse isn't true. So perhaps a non-species-using
Promise.all
would be a more useful building block.
I'd appreciate further opinions here. My bias is against trying to make
late changes to the ES6 spec, so perhaps I'm rationalizing away the
potential problems with Promise.all
and Promise.race
.
It would help to have a concrete motivating example where the Promise subclass were usefully distinct from the Promise subclass' species.
Mark: I outlined two of these use cases in esdiscuss.org/topic/subclassing-es6-objects-with-es5-syntax#content-50
One is WeakPromise
which is a promise holding a weak reference to its
resolved value. This is the closest analogy with the canonical Smalltalk
motivating example for species.
Another is TimeoutPromise
which is a promise that rejects in a fixed time
if not resolved before then.
Both of these would set WeakPromise[Symbol.species] = TimeoutPromise[Symbol.species] = Promise;
so that the weak
reference/timeout is not "contagious" across then()
.
WeakPromise.all([...])
reads like it should return a WeakPromise
(ie,
ignore species). This is consistent with the "static methods ignore
species" rule.
However, WeakPromise.resolve([....]).all()
makes it clearer that weak
reference held by WeakPromise
is actually just to the initial array, and
that the result of WeakPromise.prototype.all
should in fact be a
Promise
(ie, honor species).
Similarly for TimeoutPromise
: TimeoutPromise.resolve([...]).all()
implies a timeout to resolve the initial array-of-promises, but the result
of the all()
is a normal Promise and won't timeout. But on the other
hand, you wouldn't be wrong if you thought that TimeoutPromise.all([...])
should timeout the result of the all.
So, to me it seems like if you think that Promise.all(x)
is shorthand for
a future this.resolve(x).all()
, then honoring the species is not a
terrible thing. In ES7-ish, when you allow Promise.all
and
Promise.race
to accept Promises as their arguments (in addition to
iterables), then you could clarify the spec to indicate that the species is
ignored when resolving the Promise argument, then honored when processing
the all
or race
.
But if you want a crisp semantics and weren't afraid of more last-minute
changes to the Promise spec, then you'd define Promise.all
and
Promise.race
to ignore species, and handle the species issue in ES7 when
you define Promise.prototype.all
and Promise.prototype.race
.
IMO, at least.
On Jun 9, 2015, at 7:30 AM, C. Scott Ananian wrote:
Promise.resolve
doesn't use the species pattern any more: esdiscuss.org/topic/fixing-promise-resolve The rationale was thatresolve
is more like a constructor than a mutator.I don't have a strong opinion about
Promise.all
, but I think perhaps it would be better if none of the static methods ofPromise
(Promise.all, Promise.race, Promise.reject) used the species pattern, which is more appropriate for instance methods (likePromise.prototype.then
, where it is a natural fit).
I generally agree. You invoke one of these methods on a specific Promise constructor, presumably with the intend that it is an instance to that constructor you expect to get back.
For example, compare the likely intent of: WeakPromise.all(iterator) and Promise.all(iterator)
It seems just like what we discussed for Promise.resolve
On Tue, Jun 9, 2015 at 7:58 AM, C. Scott Ananian <ecmascript at cscott.net>
wrote:
Mark: I outlined two of these use cases in esdiscuss.org/topic/subclassing-es6-objects-with-es5-syntax#content-50
One is
WeakPromise
which is a promise holding a weak reference to its resolved value. This is the closest analogy with the canonical Smalltalk motivating example for species.Another is
TimeoutPromise
which is a promise that rejects in a fixed time if not resolved before then.Both of these would set
WeakPromise[Symbol.species] = TimeoutPromise[Symbol.species] = Promise;
so that the weak reference/timeout is not "contagious" acrossthen()
.
Ok, thanks for the example. I understand your rationale. This is what I asked for but not what I was really looking for. Can we come up with an example where class and species usefully differ where neither is Promise? IOW, where the species a proper subclass of Promise and a proper superclass of the class in question.
Btw, Allen's conclusion sounds right to me. But I'd like to see such an example, or see us fail to find one, before considering the what-it-should-be-in-the-absence-of-timing issue settled. Thanks.
Given the timing, unless the case is very strong, which you clearly doubt as well, I'm sure we won't actually revisit this for ES6, and thus never.
On Tue, Jun 9, 2015 at 11:11 AM, Mark S. Miller <erights at google.com> wrote:
Ok, thanks for the example. I understand your rationale. This is what I asked for but not what I was really looking for. Can we come up with an example where class and species usefully differ where neither is Promise? IOW, where the species a proper subclass of Promise and a proper superclass of the class in question.
www.mimuw.edu.pl/~sl/teaching/00_01/Delfin_EC/Glossary.htm#S says:
species: The generic class of an object. This is normally the same as the
class of an object, otherwise it is a class which describes the generic
group of classes to which a particular class belongs. Note that this does
not necessarily have to be class from the same branch of the hierarchy.
But I was not able to find this any examples where the species was not "from the same branch of the hierachy" in my smalltalk research. The only examples I could dig up had the species equal to the top-level superclass for the "generic group", like Array, Promise, etc.
But I'm not a smalltalk expert, by any means. I hope that some of the smalltalkers among us (or those who work with former smalltalkers?) might be able to come up with more examples.
That said, I think the WeakPromise and TimeoutPromise examples are reasonably compelling.
Further, note that if you are using the prfun
library, the "Promise" you
are using is actually a subclass of globals.Promise
. This is so that
prfun
can add all of its fun Promise.try
etc methods without polluting
the global Promise
.
So in that case: PrFunPromise extends Promise
, TimeoutPromise extends PrFunPromise
, and TimeoutPromise[Symbol.species] = PrFunPromise
.
But that's probably not really what you were looking for, either.
On Tue, Jun 9, 2015 at 11:04 AM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
I generally agree. You invoke one of these methods on a specific Promise constructor, presumably with the intend that it is an instance to that constructor you expect to get back.
For example, compare the likely intent of: WeakPromise.all(iterator) and Promise.all(iterator)
It seems just like what we discussed for Promise.resolve
I generally agree, and would not object to travelling down this path.
However, note that the implementations given for Promise.all
and
Promise.race
use Promises in two different ways: (1) every element of the
array argument has C1.resolve(elem).then(....)
applied to it, and (2) the
result is constructed with NewPromiseCapability(C2)
. It's not entirely
clear to me that C1 and C2 should be the same. Consider the
TimeoutPromise
: do we want to apply a timeout to every element
individually and to the result as a whole?
It seems that C1 might use species (to avoid the timeout/weak reference), but C2 ignore it (to apply the timeout/weak reference to the final result)
The idea that WeakPromise.all(x)
means WeakPromise.resolve(x).all()
is
a little bit surprising (because the constructed WeakPromise
is not
visible externally and thus the result honors species and is not itself a
WeakPromise
), but does have a consistency of its own. So I'm torn.
But if we decided that WeakPromise.all()
should return a WeakPromise
then I'd suggest that we change step 6 from:
Let promiseCapability be NewPromiseCapability(C).
to
Let promiseCapability be NewPromiseCapability(this).
in both Promise.all
and Promise.race
, but leave the definition of C
otherwise alone, so that internally-created promises honor the species.
I know I'm being picky here, but if timeout-ness is not intended to propagate, which seems sensible, then why would I ever want to invent a TimeoutPromise subclass rather than using a combinator like delay or race on a plain Promise?
Mark: The prfun
library in fact uses Promise#timeout(n)
instead of a
TimeoutPromise
subclass. But this is really a language-design question.
You might as well ask why we have WeakMap()
as a constructor instead of
using Map#weak()
or weakmapify(map)
. The fundamental reason is "so you
can name (and thus test) the type of the object".
But this is really a question for the smalltalk folks. All I know is from googling "smalltalk species", for example: computer-programming-forum.com/3-smalltalk/fbb2ef6357ba0905.htm
On Tue, Jun 9, 2015 at 9:29 AM, C. Scott Ananian <ecmascript at cscott.net>
wrote:
Mark: The
prfun
library in fact usesPromise#timeout(n)
instead of aTimeoutPromise
subclass. But this is really a language-design question. You might as well ask why we haveWeakMap()
as a constructor instead of usingMap#weak()
orweakmapify(map)
. The fundamental reason is "so you can name (and thus test) the type of the object".
Do you ever test that the object returned by Promise#timeout(n)
is
something other than a plain promise?
On Tue, Jun 9, 2015 at 12:38 PM, Mark S. Miller <erights at google.com> wrote:
Do you ever test that the object returned by
Promise#timeout(n)
is something other than a plain promise?
Responded on the other thread.
Let's keep this one focused on: do we need to tweak the definitions of
Promise.all
and Promise.race
as they relate to species, and if so: how?
Option 1: don't consult species at all; Option 2: use species for intermediate results, but don't use species for final result; Option 3: you tell me!
Since I'm personally rationalizing away the current behavior as grody but acceptable, I'm particularly interested in hearing "me, toos" (via private email to me or Allen) if you think we should make a change.
On Jun 9, 2015, at 9:53 AM, C. Scott Ananian wrote:
On Tue, Jun 9, 2015 at 12:38 PM, Mark S. Miller <erights at google.com> wrote: Do you ever test that the object returned by
Promise#timeout(n)
is something other than a plain promise?Responded on the other thread.
Let's keep this one focused on: do we need to tweak the definitions of
Promise.all
andPromise.race
as they relate to species, and if so: how?Option 1: don't consult species at all; Option 2: use species for intermediate results, but don't use species for final result; Option 3: you tell me!
Since I'm personally rationalizing away the current behavior as grody but acceptable, I'm particularly interested in hearing "me, toos" (via private email to me or Allen) if you think we should make a change. --scott
I agree that the current behavior is "grody, but acceptable" for Promise.all and Promise.race
However, I think that Promise.reject should parallel Promise.resolve and hence it should not use species.
On Wed, Jun 10, 2015 at 10:37 AM, Allen Wirfs-Brock <allen at wirfs-brock.com>
wrote:
However, I think that Promise.reject should parallel Promise.resolve and hence it should not use species.
I agree.
On Wed, Jun 10, 2015 at 7:37 AM, Allen Wirfs-Brock <allen at wirfs-brock.com>
wrote:
On Jun 9, 2015, at 9:53 AM, C. Scott Ananian wrote:
On Tue, Jun 9, 2015 at 12:38 PM, Mark S. Miller <erights at google.com> wrote:
Do you ever test that the object returned by
Promise#timeout(n)
is something other than a plain promise?Responded on the other thread.
Let's keep this one focused on: do we need to tweak the definitions of
Promise.all
andPromise.race
as they relate to species, and if so: how?Option 1: don't consult species at all; Option 2: use species for intermediate results, but don't use species for final result; Option 3: you tell me!
Since I'm personally rationalizing away the current behavior as grody but acceptable, I'm particularly interested in hearing "me, toos" (via private email to me or Allen) if you think we should make a change. --scott
I agree that the current behavior is "grody, but acceptable" for Promise.all and Promise.race
I agree that it is acceptable. Since we're not going to change it, I withhold judgement on whether it is goofy ;).
However, I think that Promise.reject should parallel Promise.resolve and hence it should not use species.
I agree.
people.mozilla.org/~jorendorff/es6-draft.html#sec-performpromiseall, people.mozilla.org/~jorendorff/es6-draft.html#sec-performpromiseall
I’m wondering: Is it OK that PerformPromiseAll invokes
resolve()
viaC.resolve()
(versusthis.resolve()
) withC
determined via the species pattern?Rationale:
resolve()
uses the species pattern, too (which is whythis.resolve()
works well). Therefore, the species pattern is used twice, which may lead to unexpected effects: You define the species ofthis
to be X, but the species of X is Y. ThenPromise.all()
creates an array with instances of Y, not X.