Subclassing ES6 objects with ES5 syntax.
C. Scott Ananian
ecmascript at cscott.net
Wed Apr 29 21:36:50 UTC 2015
On Wed, Apr 29, 2015 at 4:16 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>
wrote:
> On Apr 29, 2015, at 12:40 PM, C. Scott Ananian wrote:
>
> On Wed, Apr 29, 2015 at 3:32 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>
> wrote:
>
>> On Apr 29, 2015, at 12:24 PM, C. Scott Ananian wrote:
>>
>> On Wed, Apr 29, 2015 at 3:09 PM, Allen Wirfs-Brock <allen at wirfs-brock.com
>> > wrote:
>>
>>> class DefensivePromise extends Promise {
>>> constructor(x) {
>>> super(x);
>>> if (new.target === DefensivePromise) {
>>>
>> // I'm assuming this test is just to be subclass friendly and
>> allow subclasses to freeze later?
>>
>>> Object.freeze(this);
>>> }
>>> }
>>> static resolve(x) {
>>> switch (true) {
>>> default:
>>>
>> // I guess a do { } while(false); would work as well?
>>
>>> // assuming frozen primordial
>>> if (x.constructor !== DefensivePromise) break; //just a quick
>>> exit, doesn't guarantee much
>>> if (!Object.isFrozen(x)) break;
>>> If (Object.getOwnPropertyDescriptor(x, 'then')) break;
>>> //a more subclass friendly approach would walk x's
>>> [[Prototype]] chain to ensure that the correct 'then' is inherited from
>>> frozen prototypes
>>> if (Object.getPrototypeOf(x) !== DefensivePromise.prototype)
>>> break;
>>> //Assert: x.then === Promise.prototype.then, now and forever
>>> after
>>> return x;
>>> }
>>> // must be called on a subclass of DefensivePromise, so we don't
>>> need to enforce the 'then' invariant
>>> If (x.constructor === this) return x; //in which case a
>>> constructor check is good enough
>>>
>> // ^^ this is a mistake right? I think this line doesn't
>> belong.
>>
>>> return new this(r => {r(x)});
>>> }
>>> }
>>> Object.freeze(DefensivePromise);
>>> Object.freeze(DefensivePromise.prototype);
>>>
>>
>> It's not clear what the `x.constructor` test is still doing in your
>> implementation.
>>
>>
>> Do you mean the first or second occurrence?
>>
>
> The second occurrence. At that point all we know about x is that it's
> *not* something safe. We shouldn't be looking at `constructor` in that
> case.
>
>
> the last two lines of the above `resolve` method should be replaced with:
> return super.resolve(x)
>
> x.construct === this is the test that I propose Promise.resolve should
> make instead of testing [[PromiseConstructor]]
> There should probably also be another test that
> SpeciesConstrutor(x)===this
>
This is still not correct.
You can't call `super.resolve(x)` safely unless you know that
this.constructor is trustworthy. If you've bailed out of the `switch`, you
don't know that.
A correct implementation just does `return new this(r => { r(x); })` after
the switch.
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.
I wouldn't jump the gun until we actually have TC39 consensus on a proposal.
>
Mark, after you've slept on this and assuming you haven't changed your
mind, would you be willing to be the TC39 champion? I'm not a participant
in that process.
> What we've shown is that the [[PromiseConstructor]] test was never
> sufficient to guarantee the invariant that Mark is concerned about and also
> that there probably isn't a generic check we can do that would satisfy his
> requirements. He has do it himself like shown above. But the specified
> [[PromiseConstructor]] test doesn't really hurt anything. It places some
> slight limitations on what subclasses can do, but probably doesn't
> interfere with anything an actual subclass is likely to do. It seems like
> a restriction than can be relaxed in the future without anybody (or than
> conformance tests) noticing.
>
Well, the [[PromiseConstructor]] test is breaking `prfun`, `es6-shim` and
`core-js` right now, which is why I noticed it in the first place. (v8
seems to have implemented the `[[PromiseConstructor]]` internal property
even though they don't have `new.target` implemented, so it's always
hard-wired to `Promise`.) And `babel` and `core-js` have implemented
non-standard semantics for `Promise.resolve` as a result.
In particular, with the current state of `Promise.resolve`, I can't
subclass `Promise` in a meaningful way using ES5 syntax (see, the thread
title turns relevant again!). So I have a vested interest in getting the
semantics fixed so that people can start using `Promise` subclasses (like
`prfun` does) instead of writing their "extra" Promise helpers directly on
the `Promise` global object, while we're all waiting for ES6 syntax to make
it to the mainstream.
(And when I say "getting the semantics fixed" what I really mean is
"consensus among TC39" so that shim libraries can implement the correct
semantics as early adopters, with assurance the browser engines will
follow.)
Since I believe the goal is to eventually introduce some new methods on the
global `Promise` in ES7, I think this list would have a keen interest in
ensuring that libraries which stomp on the global Promise (due to lack of
an alternative) don't take root.
--scott
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20150429/38306d40/attachment-0001.html>
More information about the es-discuss
mailing list