Typescript: Extending Promise gives runtime error: undefined is not a promise

Created on 15 Apr 2017  路  4Comments  路  Source: microsoft/TypeScript

I also asks this on Stackoverflow, but I think this is a bug:

I'm trying to cancel my async method call in Typescript.

To do this, I have created a new Promise type, which inherits from Promise.

My first version was without the Object.setPrototypeOf(..), but I took it for here

TypeScript Version: 2.2.0, targeting ES5

Code

class CancelablePromise<T> extends Promise<T>{

    public cancelMethod: () => void;
    constructor(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void) {
        super(executor);
        // Set the prototype explicitly.
        // See: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
        Object.setPrototypeOf(this, CancelablePromise.prototype);

    }

    //cancel the operation
    public cancel() {
        if (this.cancelMethod) {
            this.cancelMethod();
        }
    }
}

class Test{

    async postFileAjax<T>(file: File): CancelablePromise <T> { 

        var promiseFunc = (resolve) => { resolve() };
        var promise = new CancelablePromise<T>(promiseFunc);
        promise.cancelMethod = () => { console.log("cancel!") };

        return promise;
    }
}

var test = new Test();
test.postFileAjax(null);

Expected behavior:
No runtime error, or compile error if not allowed.

And of course working promise if no error.

Actual behavior:

Got a run-time error

Error:

(unknown) Uncaught TypeError: undefined is not a promise
    at CancelablePromise.Promise (<anonymous>)
    at new CancelablePromise (<anonymous>:44:28)
    at __awaiter (<anonymous>:7:12)
    at Test.postFileAjax (<anonymous>:62:16)
    at <anonymous>:75:6
    at HTMLButtonElement.excuteButton.onclick (https://www.typescriptlang.org/play/playground.js:242)

Link to playground

Bug ES6

Most helpful comment

FWIW, another workaround that seems to work is wrapping the promise in a class that's a "thenable". Here's one for a Deferred promise:

export default class Deferred<T> {
  private res: (value?: T | PromiseLike<T>) => void
  private rej: (reason?: any) => void
  private readonly promise: Promise<T>

  constructor() {
    this.promise = new Promise((resolve, reject) => {
      this.res = resolve
      this.rej = reject
    })
  }

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

  catch(onRejected?: (reason: any) => PromiseLike<never>): Promise<T> {
    return this.promise.catch(onRejected)
  }

  resolve(value?: T | PromiseLike<T>): void {
    return this.res(value)
  }

  reject(reason?: any): void {
    return this.rej(reason)
  }
}

Seems to work with async and whatnot.

All 4 comments

The issue here is that ES6 classes (e.g. Promise) are not callable, the way ES5 built in classes (e.g. Array). The emitted code that the compiler produces uses super.call which throws in ES6 engine.

The fix here is to use Reflect.construct if it exists instead of call. Reflect.construct is not something we can polifill, but we can assume if built-in Promise is there, Refelect.construct is there too.

Here is a proposal for that from @rbuckton:

var __construct = (this && this.__construct) || (typeof Reflect !== "undefined" && Reflect.construct
    ? function (self, target, args) { return self !== null && Reflect.construct(target, args, self.constructor) || self; }
    : function (self, target, args) { return self !== null && target.apply(self, args) || self; });

var PatchedPromise = (function (_super) {
    __extends(PatchedPromise, _super);
    function PatchedPromise(executor) {
        var _this = this;
        _this = __construct(this, _super, [executor]);
        return _this;
    }
    return PatchedPromise;
}(Promise));

I have filed #15397 for this proposal.

FWIW, another workaround that seems to work is wrapping the promise in a class that's a "thenable". Here's one for a Deferred promise:

export default class Deferred<T> {
  private res: (value?: T | PromiseLike<T>) => void
  private rej: (reason?: any) => void
  private readonly promise: Promise<T>

  constructor() {
    this.promise = new Promise((resolve, reject) => {
      this.res = resolve
      this.rej = reject
    })
  }

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

  catch(onRejected?: (reason: any) => PromiseLike<never>): Promise<T> {
    return this.promise.catch(onRejected)
  }

  resolve(value?: T | PromiseLike<T>): void {
    return this.res(value)
  }

  reject(reason?: any): void {
    return this.rej(reason)
  }
}

Seems to work with async and whatnot.

closing in favor of #15397

Was this page helpful?
0 / 5 - 0 ratings