Promise sub-class: super((resolve, reject) => this) ?

# Matthew Robb (9 years ago)

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?

# Sebastian McKenzie (9 years ago)

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

# Domenic Denicola (9 years ago)

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
# Sebastian McKenzie (9 years ago)

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.

# Matthew Robb (9 years ago)

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 :(

# Brendan Eich (9 years ago)

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?

# Logan Smyth (9 years ago)

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?

# Kevin Smith (9 years ago)

that's correct.

# Allen Wirfs-Brock (9 years ago)

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 the this 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:

  1. 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;
  1. compile ever reference to this within such constructors as $$this();

  2. wrap every call to the super constructor within such constructors as: $$superCalled(<the emitted code to make the call>)

# Benjamin Gruenaum (9 years ago)

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

# Matthew Robb (9 years ago)

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?

# John Barton (9 years ago)

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.