Deferred that extends Promise

2019-08-13 19:57发布

问题:

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.

回答1:

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.then(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 from the compiler.



回答2:

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;
  }
}