PerformPromiseAll

classic Classic list List threaded Threaded
14 messages Options
Reply | Threaded
Open this post in threaded view
|

PerformPromiseAll

Axel Rauschmayer
https://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.

-- 
Dr. Axel Rauschmayer
[hidden email]
rauschma.de




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: PerformPromiseAll

C. Scott Ananian
`Promise.resolve` doesn't use the species pattern any more: https://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`.
  --scott


On Tue, Jun 9, 2015 at 6:46 AM, Axel Rauschmayer <[hidden email]> wrote:
https://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.

-- 
Dr. Axel Rauschmayer
[hidden email]
rauschma.de




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss



_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: PerformPromiseAll

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


On Tue, Jun 9, 2015 at 7:30 AM, C. Scott Ananian <[hidden email]> wrote:
`Promise.resolve` doesn't use the species pattern any more: https://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`.
  --scott


On Tue, Jun 9, 2015 at 6:46 AM, Axel Rauschmayer <[hidden email]> wrote:
https://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.

-- 
Dr. Axel Rauschmayer
[hidden email]
rauschma.de




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss



_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss




--
    Cheers,
    --MarkM

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: PerformPromiseAll

C. Scott Ananian
Mark: I outlined two of these use cases in https://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.
  --scott


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: PerformPromiseAll

Allen Wirfs-Brock
In reply to this post by C. Scott Ananian

On Jun 9, 2015, at 7:30 AM, C. Scott Ananian wrote:

`Promise.resolve` doesn't use the species pattern any more: https://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

Allen



_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: PerformPromiseAll

Mark S. Miller-2
In reply to this post by C. Scott Ananian
On Tue, Jun 9, 2015 at 7:58 AM, C. Scott Ananian <[hidden email]> wrote:
Mark: I outlined two of these use cases in https://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.



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




--
    Cheers,
    --MarkM

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: PerformPromiseAll

C. Scott Ananian
On Tue, Jun 9, 2015 at 11:11 AM, Mark S. Miller <[hidden email]> 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.

> 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 <[hidden email]> 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.
  --scott


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: PerformPromiseAll

Mark S. Miller-2
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?


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: PerformPromiseAll

C. Scott Ananian
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: http://computer-programming-forum.com/3-smalltalk/fbb2ef6357ba0905.htm
  --scott

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: PerformPromiseAll

Mark S. Miller-2


On Tue, Jun 9, 2015 at 9:29 AM, C. Scott Ananian <[hidden email]> 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?

 

But this is really a question for the smalltalk folks.  All I know is from googling "smalltalk species", for example: http://computer-programming-forum.com/3-smalltalk/fbb2ef6357ba0905.htm
  --scott



--
    Cheers,
    --MarkM

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: PerformPromiseAll

C. Scott Ananian
On Tue, Jun 9, 2015 at 12:38 PM, Mark S. Miller <[hidden email]> 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


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: PerformPromiseAll

Allen Wirfs-Brock

On Jun 9, 2015, at 9:53 AM, C. Scott Ananian wrote:

On Tue, Jun 9, 2015 at 12:38 PM, Mark S. Miller <[hidden email]> 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.

Allen


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: PerformPromiseAll

C. Scott Ananian
On Wed, Jun 10, 2015 at 10:37 AM, Allen Wirfs-Brock <[hidden email]> wrote:
However, I think that Promise.reject should parallel Promise.resolve and hence it should not use species.

I agree.
  --scott


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: PerformPromiseAll

Mark S. Miller
In reply to this post by Allen Wirfs-Brock


On Wed, Jun 10, 2015 at 7:37 AM, Allen Wirfs-Brock <[hidden email]> 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 <[hidden email]> 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.
 

-- 
  Cheers,
  --MarkM

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss