Subclassing ES6 objects with ES5 syntax.
allen at wirfs-brock.com
Tue Apr 28 18:41:11 UTC 2015
On Apr 27, 2015, at 8:01 PM, Kevin Smith wrote:
> x = Reflect.construct(Promise, x, C);
> is another fine way to fool someone who wrote "C.resolve(x)" and expected to get an instance of C back.
> Thanks for pointing this out. I believe the ability to use an arbitrary newTarget parameter for Reflect.construct is breaking the intent of Promise.resolve. Using an arbitrary "newTarget" is also problematic for the private fields proposal.
> It seems to me that Reflect.construct has been given a capability that is not otherwise expressible with ES6 syntax, and that gap is problematic.
> Maybe I've missed some context though. Mark, Allen, any thoughts?
Note that the exact same kind of circumvention could have been accomplished with the Promise design prior to instantiation reform adding the 3rd argument to Construct:
x = C[Symbol.create]().apply(Promise, x);
As to "breaking the intent of Promise.resolve", we have to look closely at what that intent actually is and whether "intent" is equivalent to "guarantee". Apparently the intent of Foo.resolve is to coerce the argument value to a the same type of promise object as produced by the Foo constructor (assuming that Foo is a constructor that inherits from Promise). But "same type" is a very elusive concept of in JS. Is an object that is initially instantiated by Foo still of the "same type" as other objects created by Foo if it has been mutated that such that it no longer has Foo.prototype on its prototype chain and/or shares no properties in common with the other objects created using Foo. If an object created by Foo still the "same type" if additional properties are added to it. What if addiotnal properties are added via subclassing. Why is an instance of a subclass of Foo the "same type" as an instance of Foo. If not, why not? Finally, note Foo.resolve may or may not be the same method as Promise.resolve, so whatever definition of "same type" that is used by Promise.resolve is not necessarily the definition used by Foo.resolve.
So, ES6 Promises reflect a specific set of design decisions, including a specific definition of "same type" that appears to exist solely for use by Promise.resolve. All that design guarantees is that the object has an certain specific internal slot whose value is tested in a specific way by Promise.resolve. It doesn't guarantee that the object is a well-promise or that it has any specific other characteristics that are expected of Foo objects. It's hard to extrapolate from that specific design to the underlying intent of the designer.
This is the nature of JS objects. There are very few guarantees about anything. If you want guarantees you need to lock things down and probably add code to test for the guaranteed conditions. If you want guarantees, you probably aren't going to use polymeric then-ables or any other form of OO polymorphism. but, it's the developers choice.
> Looking over the Reflect namespace, I also see that Reflect.get and Reflect.set have been given powers not expressible with syntax: the receiver does not have to be a prototype parent of the target.
The Receiver argument to these Reflect.get/set/construct is necessary for many Proxy handler use cases.
These are simply a reification of the ES MOP api and an important use case of them is to define exotic objects whose behaviors differ from what can be directly expressed using ES language level syntax.
It's a trade-up, if you want to allow ES programmer to express things beyond that the syntactic language supports you can't limit that expression to what the syntax allows.
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the es-discuss