PerformPromiseAll

# Axel Rauschmayer (6 years ago)

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() via C.resolve() (versus this.resolve()) with C determined via the species pattern?

Rationale: resolve() uses the species pattern, too (which is why this.resolve() works well). Therefore, the species pattern is used twice, which may lead to unexpected effects: You define the species of this to be X, but the species of X is Y. Then Promise.all() creates an array with instances of Y, not X.

# C. Scott Ananian (6 years ago)

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.

# Mark S. Miller (6 years ago)

It would help to have a concrete motivating example where the Promise subclass were usefully distinct from the Promise subclass' species.

# C. Scott Ananian (6 years ago)

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.

# Allen Wirfs-Brock (6 years ago)

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 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).

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

# Mark S. Miller (6 years ago)

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" across then().

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.

# C. Scott Ananian (6 years ago)

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.

# Mark S. Miller (6 years ago)

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?

# C. Scott Ananian (6 years ago)

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

# Mark S. Miller (6 years ago)

On Tue, Jun 9, 2015 at 9:29 AM, C. Scott Ananian <ecmascript at cscott.net>

wrote:

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".

Do you ever test that the object returned by Promise#timeout(n) is something other than a plain promise?

# C. Scott Ananian (6 years ago)

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.

# Allen Wirfs-Brock (6 years ago)

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 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. --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.

# C. Scott Ananian (6 years ago)

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.

# Mark Miller (6 years ago)

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 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. --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.