Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extending Promise gives runtime error: undefined is not a promise #15202

Closed
304NotModified opened this issue Apr 14, 2017 · 4 comments
Closed
Labels
Bug A bug in TypeScript ES6 Relates to the ES6 Spec

Comments

@304NotModified
Copy link

304NotModified commented Apr 14, 2017

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

@mhegazy mhegazy added Bug A bug in TypeScript ES6 Relates to the ES6 Spec labels Apr 26, 2017
@mhegazy
Copy link
Contributor

mhegazy commented Apr 26, 2017

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

@rbuckton
Copy link
Member

I have filed #15397 for this proposal.

@matthewmueller
Copy link

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.

@mhegazy
Copy link
Contributor

mhegazy commented Nov 20, 2017

closing in favor of #15397

@mhegazy mhegazy closed this as completed Nov 20, 2017
@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Bug A bug in TypeScript ES6 Relates to the ES6 Spec
Projects
None yet
Development

No branches or pull requests

4 participants