Promise sub-class: super((resolve, reject) => this) ?
This is a limitation of Babel and not at all a reflection of the actual specification. This restriction is imposed order to follow ES2015 semantics of not being able to reference this
before super()
. It does a pretty dumb check of only allowing it to be strictly after the call (ie. not before or inside it). Note that this is also the behaviour of Traceur and TypeScript so Babel is not alone with this decision. You can see extensive discussion of this in the issue: babel/babel#1131
Hmm I am pretty sure Babel et al. are correct here in not allowing this. The super call needs to finish before you can use this
. Chrome also works this way.
The correct workaround is
let resolve, reject;
super((a, b) => {
resolve = a;
reject = b;
});
// use this
Ah, completely right. At first glance I thought it was this similar but separate issue:
class Foo {
constructor(callback) {
this.callback = callback; // just storing it!
}
}
class Bar extends Foo {
constructor() {
super(() => this); // reference to `this` will throw since the behaviour of the super class can’t be easily inferred
}
}
Shifting runtime errors to compile time isn’t always the most reliable.
If I thought I could make any money then I would most definitely bet that the changes made to classes that are at the root of this problem will be the undoing of es classes and I find myself feeling more and more like avoiding them is the easiest thing to do.
This use-case is a perfect example of something that is EXTREMELY unexpected which is funny because the changes are supposed to be supporting subclassing of built-ins.
Very disheartened :(
With best intentions I must say that you are overreacting. The
subject-line code (h/t Mark Miller for pointing me at it!) in context of
the superclass constructor uses this
before super
has returned.
That's a no-no for pretty-good reason.
If you have a better alternative design, we needed it last month. As things stand, this is a thing to learn, with a workaround. What's the big deal?
To clarify things, since I don't think it's been made abundantly clear, the
example that Sebastian gave would work in a standard ES6 environment,
correct? It is only if the callback were executed synchronously that the
exception would be thrown since the this
binding has not yet been
initialized?
Transpilers however have elected to prevent this to err on the side of
ensuring that invalid ES6 allowed through because adding runtime checking
for the this
binding would be difficult?
that's correct.
On Jun 2, 2015, at 8:08 PM, Logan Smyth wrote:
To clarify things, since I don't think it's been made abundantly clear, the example that Sebastian gave would work in a standard ES6 environment, correct? It is only if the callback were executed synchronously that the exception would be thrown since the
this
binding has not yet been initialized? Transpilers however have elected to prevent this to err on the side of ensuring that invalid ES6 allowed through because adding runtime checking for thethis
binding would be difficult?
In other words, transpilers have elected to be buggy. ECMAScxript 2015 does not given implementations an option in this regard. If an implementation produces such an early error it is not in compliance with the standard.
It doesn't seem like it should be very hard for transpilers to correctly implement the derived constructor this-TDZ semantics. For example, here is a plausible strategy:
- As a prologue to each constructor body defined in a class definition that includes an extends clause emit:
let $$thisAlive = false;
let $$this = ()=> { if ($$thisAlive) then return this; else throw ReferenceError"this referenced before super call completes")};
let $$superCalled = r=> $$thisAlive = true, r;
-
compile ever reference to
this
within such constructors as$$this()
; -
wrap every call to the super constructor within such constructors as:
$$superCalled(<the emitted code to make the call>)
Am I missing something obvious in super((resolve, reject) => this)
?
First of all, it makes perfect sense for this
not work work before super
has been called - and it has not been called yet. I think that the crux is
that the promise constructor runs synchronously so when you pass it
this
it has not finished running yet.
Of course, the workaround as domenic has pointed is to extract resolve
and reject
from the super
call since it is synchronous.
(also I'm assuming you're not really mapping (resolve, reject)
to this
?
this
is an object and the promise constructor ignores return values
anyway, you might as well pass a no-op in.)
It seems like, at least in the case with Promise, it could be solved also by having the this binding of the executor bound to the promise or have the promise object passed into the executor as a third argument maybe?
On Wed, Jun 3, 2015 at 1:27 AM, Benjamin Gruenaum <benjamingr at gmail.com> wrote:
Am I missing something obvious in
super((resolve, reject) => this)
?First of all, it makes perfect sense for
this
not work work before super has been called - and it has not been called yet.
Rather than starting off by claiming the restriction on "this" before "super()" makes perfect sense, let's be honest that lots of normal developers will be tripped up by this restriction. "this" is used a reference throughout class code: it makes "perfect sense" to use it in a constructor. Passing extended-class-specific values to super() is exactly what super() does. Thus using "this" in a constructor before calling super() is entirely natural in light of other experience with the language. Our inability to support "this" before super() is unfortunate, so let's sympathize and encourage people to understand.
I was trying to demonstrate a simple method of exposing resolve and reject functions to someone and noticed in Babel at least you cannot do this. It seems as though in this case when the arrow function is called it will have been AFTER the call to super.
Can someone help me understand what's going on here?