5

How to implement a Deferred promise that extends Promise? It's important to extend Promise for type-safe usage where a typical Promise is expected.

Following implementation

export class Deferred<T> extends Promise<T> {                                   
  public _resolveSelf;
  public _rejectSelf;                                                           
  constructor() {
    super(
      (resolve, reject) =>
      {
        this._resolveSelf = resolve
        this._rejectSelf = reject
      }
    )
  }                                                                             
  public resolve(val:T) { this._resolveSelf(val) }
  public reject(reason:any) { this._rejectSelf(reason) }                        
}

throws TypeError: _this is undefined.

In this Typescript playground, one can see that the compiled javascript is funny. In line 15, during declaration of _this already its properties are being assigned.

Dominykas Mostauskis
  • 7,797
  • 3
  • 48
  • 67

2 Answers2

5
export class Deferred<T> implements Promise<T> {

  private _resolveSelf;
  private _rejectSelf;
  private promise: Promise<T>

  constructor() {
    this.promise = new Promise( (resolve, reject) =>
      {
        this._resolveSelf = resolve
        this._rejectSelf = reject

      }
    )
  }

  public then<TResult1 = T, TResult2 = never>(
    onfulfilled?: ((value: T) =>
      TResult1 | PromiseLike<TResult1>) | undefined | null,
    onrejected?: ((reason: any) =>
      TResult2 | PromiseLike<TResult2>) | undefined | null
    ): Promise<TResult1 | TResult2> {
      return this.promise.then(onfulfilled, onrejected)
    }

  public catch<TResult = never>(
    onrejected?: ((reason: any) =>
      TResult | PromiseLike<TResult>) | undefined | null
    ): Promise<T | TResult> {
      return this.promise.catch(onrejected)
    }

  public resolve(val:T) { this._resolveSelf(val) }
  public reject(reason:any) { this._rejectSelf(reason) }

  [Symbol.toStringTag]: 'Promise'

}

Method signatures of then and catch copy-pasted from Typescript's Promise interface and cosmetically cleaned up. The [Symbol.toStringTag] line is also necessary for Typescript to consider this a Promise, though you only find that out at compile-time.

Michael Fry
  • 1,090
  • 9
  • 12
Dominykas Mostauskis
  • 7,797
  • 3
  • 48
  • 67
4

How to implement a Deferred promise that extends Promise?

Better not at all. There is absolutely no good reason to use deferreds.

And even then, a deferred that is a promise is a bad idea, you should separate the capabilities:

function defer() {
    var deferred = {};
    deferred.promise = new Promise(resolve => {
        deferred.resolve = resolve;
    });
    return deferred;
}

One can see that the compiled javascript is funny. In line 15, during declaration of _this already its properties are being assigned.

Yes, that's how super works: you cannot use this until the parent constructor call returned and intitialised it. The promise executor callback gets called before that however.

You'd have to write

export class Deferred<T> extends Promise<T> {
  public resolve: T=>void;
  public reject: any=>void;
  constructor() {
    var resolveSelf, rejectSelf
    super((resolve, reject) => {
      resolveSelf = resolve
      _rejectSelf = reject
    })
    this.resolve = resolveSelf;
    this.reject = rejectSelf;
  }
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 13
    Deferred decouples promise declaration and resolution (rejection). Surely you can think of situations where that's useful. 95% of the time, promises are preferable. They are simpler and are a better general tool, that's what the question you linked to explains. That doesn't imply they don't have their uses. – Dominykas Mostauskis Jul 04 '17 at 15:32
  • 2
    @DominykasMostauskis Even for most of the other 5%, the decoupled `resolve` function - which can be stored and passed around as well - is already enough. Needing both the resolver and the promise in the same data structure (like my `defer()`) is really really rare. Maybe even so rare that a dedicated helper function isn't worth it. – Bergi Jul 04 '17 at 15:44
  • Totally agree with @DominykasMostauskis here. The article you link to Bergi doesn't even say there is no good reason to use deferreds you just made that part up. – John Culviner Dec 20 '22 at 16:43
  • @JohnCulviner Ok, maybe I should've phrased it differently. The answer I linked gives "*a good reason not to use deferreds*". That there also are no good reasons to use them is indeed my opinion. Coupling the promise and its resolution capability into a single object should be avoided. Every code that uses deferred can be written just as easily/concisely with the `Promise` constructor. – Bergi Dec 21 '22 at 00:16