0

I'm trying to create a deferred promise constructor, like so:

class DeferredPromise extends Promise {
  constructor() {
    super((resolve, reject) => {
      this.resolve = resolve
      this.reject = reject
    })
  }
}

However, when I try to use new DeferredPromise(), I get the following error in Firefox 60.0.01

ReferenceError: must call super constructor before using |this| in arrow function in derived class constructor

Why do I get this error and how can I get around it? Also, if there's a better solution to this please let me know.

robbie
  • 1,219
  • 1
  • 11
  • 25
  • 1
    The call to `super` has to happen before you reference `this`. Also not clear why you don't just use `Promise.resolve(something)` or `Promise.reject(someError)`. – Jared Smith Jun 15 '18 at 00:04
  • FYI, if you're just looking for a simple Deferred object implementation, there are several already here on stack overflow. Here's one: [Deferred implementation](https://stackoverflow.com/questions/37651780/why-does-the-promise-constructor-need-an-executor/37673534#37673534). – jfriend00 Jun 15 '18 at 00:23

1 Answers1

2

this isn't allowed in child classes before parent constructor call (super) because this doesn't certainly exist before that (parent class can return another object instead of this). This ES6 class restriction is enforced by the specification.

As the reference states,

If there is a constructor present in subclass, it needs to first call super() before using "this".

If it's known that this is used inside the function only after parent constructor call, it can be safely used:

class Foo {
  constructor(callback) {
    this.doFoo = callback;
  }
}

class Bar extends Foo {
  constructor() {
    super(() => {
      this.doBar();
    })
  }

  doBar() {}
}

new Bar().doFoo();

Otherwise this should be referred only after parent constructor call:

class DeferredPromise extends Promise {
  constructor() {
    let resolve, reject;

    super((_resolve, _reject) => {
      resolve = _resolve;
      reject = _reject;
    });

    this.resolve = resolve;
    this.reject = reject;
  }
}

This will cause a problem specific to Promise and described in this answer, the suggested solution is:

DeferredPromise.prototype.constructor = Promise;

This means that DeferredPromise instance needs to be stored to access resolve and reject methods, they won't be available in chained promises:

let deferred = new DeferredPromise();
let nonDeferred = deferred.catch(() => {});
// nonDeferred.reject === undefined

This is the case for composition over inheritance. Deferred pattern doesn't need promise class to be extended, doing that has no obvious benefits. An implementation can be as simple as:

class Deferred {
  constructor() {
    this.promise = new Promise((resolve, reject) => {
      this.resolve = resolve;
      this.reject = reject;
    });
  }
}
Estus Flask
  • 206,104
  • 70
  • 425
  • 565