Subclassing ES6 objects with ES5 syntax.
C. Scott Ananian
ecmascript at cscott.net
Thu Apr 30 17:57:16 UTC 2015
On Wed, Apr 29, 2015 at 6:32 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>
wrote:
> The "another test" that you propose seems to be exactly the fix which
> Brandon proposed and I codified, to wit:
>
> 2. If IsPromise(x) is true,
>> a. Let constructor be the value of SpeciesConstructor(x, %Promise%)
>> b. If SameValue(constructor, C) is true, return x.
>
>
> That can't right. x could be an object that is not a C but whose
> SpeciesConstructor is C. In that case, a new C promise on x still has to
> be created. But your code would just return x. I would use a 'construtor'
> test in step 2. But if 'constructor' is not C and SpeciesConstructor is
> also not C then an extra level of C wrapping is needed.
>
Let's call the Brendan proposal above the "early test" proposal.
It seems to me that if `P.@@species` is `Q` then `P.resolve` should always
return an object `x` with `x.constructor==Q`, which I don't think any of
the proposed tweaks yet do. So here's that variant, which I'll call the
"late test" proposal:
Promise.resolve(x)
> 1. Let C be the this value.
> 2. If Type(C) is not Object, throw a TypeError exception.
> 3. Let S be Get(C, @@species).
> 4. ReturnIfAbrupt(S).
> 5. If S is neither undefined nor null, let C be S.
> 6. If IsPromise(x) is true,
> a. Let constructor be the value of Get(x, "constructor").
> b. ReturnIfAbrupt(constructor)
> c. If SameValue(constructor, C) is true, return x.
> 7. Let promiseCapability be NewPromiseCapability(C).
> 8. ReturnIfAbrupt(promiseCapability).
> 9. Let resolveResult be Call(promiseCapability.[[Resolve]], undefined,
> «x»).
> 10. ReturnIfAbrupt(resolveResult).
> 11. Return promiseCapability.[[Promise]].
This moves the test down from step 2 to step 6, and ensures that we test
`x.constructor` against the same species that we would use to create the
capability. The returned promise thus always has a consistent value for
its constructor property.
It's hard for me to determine which of these is actually preferred,
although both are more consistent that the current [[PromiseConstructor]]
semantics.
It would help if the ES2015 proposal actually had any objects where
`C[Symbol.species] !== C`, but it does not appear to. So let's look at the
smalltalk usage of species.
To quote an arbitrarily-chosen smalltalk glossary (
http://www.mimuw.edu.pl/~sl/teaching/00_01/Delfin_EC/Glossary.htm):
> *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.
With this meaning for `@@species`, when one sets
`MyPromise.constructor[Symbol.species] = Promise;` then one is saying that
even though this is a `MyPromise` it's still generically a `Promise` (of
the `Promise` species). Then the "early test" `Promise.resolve` is
entirely consistent in passing through anything whose species is
`Promise`. The "late test" semantics are a bit harder to describe in terms
of species (even though they seem more consistent if you are just looking
at the `constructor` property of the result).
The smalltalk motivating example usually given for species is the
equivalent of:
```
WeakArray.constructor[Symbol.species] = Array;
```
so that the result of `WeakArray.filter(...)` isn't itself a weak array and
so won't mysteriously disappear in the hands of the caller. Moving this
example to `Promise`s we'd have:
```
WeakPromise.constructor[Symbol.species] = Promise;
```
and `WeakPromise` would hold a weak reference to some value, but it
wouldn't cause the promise returned by `Promise#then` to also be a weak
reference.
Moving from the reference domain to the time domain, we can imagine that:
```
TimeoutPromise.constructor[Symbol.species] = Promise;
```
could also be used to ensure that a given promise is rejected if not
resolved within a certain time period, but the "timeout" functionality is
not intended to be inherited by promises chained with `then`.
The "early test" and "late test" proposals give different results and
interpretations for:
```
p = Promise.resolve(weakPromise);
p = Promise.resolve(timeoutPromise);
```
In the "early test" proposal, both the `weakPromise` and the
`timeoutPromise` are both already of the `Promise` species, and so don't
need to be recast. In the "late test" proposal these would both be recast
to new `Promise`s -- possibly creating a new strong reference to the value
in the `weakPromise` case.
On the other hand, both "early test" and "late test" give awkward semantics
for:
```
pp = WeakPromise.resolve(p);
pp = TimeoutPromise.resolve(p);
```
Presumably these invocations are intended to ensure that `pp` holds a weak
reference to the value or times out, respectively, even if `p` is already a
generic `Promise` ("belongs to the Promise species").
But both "early test" and "late test" semantics potentially return generic
`Promise` objects here, although the "early test" semantics do pass through
a `WeakPromise`/`TimeoutPromise` if it is passed as an argument.
I think what this is saying is that `Promise.resolve` is more like a
constructor than an instance method, and it shouldn't consult `@@species`
at all.
So here's one more proposal. We'll call this the "no species" proposal:
> Promise.resolve(x)
> 1. Let C be the this value.
> 2. If IsPromise(x) is true,
> a. Let constructor be the value of Get(x, "constructor").
> b. ReturnIfAbrupt(constructor)
> c. If SameValue(constructor, C) is true, return x.
> 3. Let promiseCapability be NewPromiseCapability(C).
> 4. ReturnIfAbrupt(promiseCapability).
> 5. Let resolveResult be Call(promiseCapability.[[Resolve]], undefined,
> «x»).
> 6. ReturnIfAbrupt(resolveResult).
> 7. Return promiseCapability.[[Promise]].
At this point I think "no species" is the correct thing to do. It also
happens to be the simplest. If someone can come up with some alternative
use cases for Promise.@@species I could perhaps be persuaded to change my
mind.
--scott
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20150430/a4f85504/attachment.html>
More information about the es-discuss
mailing list