Subclassing ES6 objects with ES5 syntax.

Allen Wirfs-Brock allen at wirfs-brock.com
Wed Apr 29 22:32:42 UTC 2015


On Apr 29, 2015, at 2:36 PM, C. Scott Ananian wrote:

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

you're correct.  Let's go with:

class DefensivePromise extends Promise {
  constructor(x) {
    super(x);
    if (new.target === DefensivePromise) {
      Object.freeze(this);
    }
  }
  static resolve(x) {
    if (this===DefensivePromise) {
       switch (true) {
          default:
             // 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;
        };
       return new this(r => {r(x)});
    }
    // must be called on a subclass of DefensivePromise, so we don't need to enforce the DefensivePromise 'then' invariant 
    return super.resolve(x);
    }
 }
Object.freeze(DefensivePromise);
Object.freeze(DefensivePromise.prototype);

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

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.



-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20150429/e31c6137/attachment-0001.html>


More information about the es-discuss mailing list