Typescript: Promise.then inference bug

Created on 20 Jul 2017  Â·  8Comments  Â·  Source: microsoft/TypeScript

TypeScript Version: 2.4.1

Code

typescript playground link

const promise = Promise.resolve(true);

const bar = () : Promise<string> => promise.then((condition) => {
    if (condition) {
        return 'foo';
    }
    return Promise.reject('bar');
});
tsc --target es6

Expected behavior:
Should compile.

Actual behavior:

src/foo.ts(3,50): error TS2345: Argument of type '(condition: boolean) => Promise<never> | "foo"' is not assignable to parameter of type '(value: boolean) => PromiseLike<never>'.
  Type 'Promise<never> | "foo"' is not assignable to type 'PromiseLike<never>'.
    Type '"foo"' is not assignable to type 'PromiseLike<never>'.
Needs Investigation

Most helpful comment

// lib.es6.d.ts
PromiseConstructor {
    // ... some code
    /**
     * Creates a new rejected promise for the provided reason.
     * @param reason The reason the promise was rejected.
     * @returns A new rejected Promise.
     */
    reject(reason: any): Promise<never>;
    // ...some code 
    resolve<T>(value: T | PromiseLike<T>): Promise<T>;
}

As you can see, Promise.reject(xxx) will return a Promise<never>. But the function bar which you declared want to return a Promise<string>.

And:

The never type is a subtype of, and assignable to, every type; however, no type is a subtype of, or assignable to, never (except never itself). Even any isn’t assignable to never.

If you use assert, The code will run successful too.

const promise = Promise.resolve(true);

const bar = () : Promise<string> => promise.then((condition) => {
    if (condition) {
        return 'foo';
    }
    return <Promise<string>>Promise.reject('bar');
});

I guess the answer is this

All 8 comments

i guess this error because of the code const promise = Promise.resolve(true);. If you add type any to promise it will be compiled.

You shouldn't use Promise because Promise is a constructor;

this code will run successful:

const promise = Promise.resolve(true);

const bar = () : Promise<string> => promise.then((condition) => {
  if (condition) {
    return 'foo';
  }
  return new Promise((resolve, reject) => {
    reject('bar');
  });
});

@MrKou47 thanks for the response. You're right, that does make the code compile, but it seems like my original example should also work, since Promise.reject is a valid es6 method on the Promise constructor. In fact, your code is semantically identical iiuc.

// lib.es6.d.ts
PromiseConstructor {
    // ... some code
    /**
     * Creates a new rejected promise for the provided reason.
     * @param reason The reason the promise was rejected.
     * @returns A new rejected Promise.
     */
    reject(reason: any): Promise<never>;
    // ...some code 
    resolve<T>(value: T | PromiseLike<T>): Promise<T>;
}

As you can see, Promise.reject(xxx) will return a Promise<never>. But the function bar which you declared want to return a Promise<string>.

And:

The never type is a subtype of, and assignable to, every type; however, no type is a subtype of, or assignable to, never (except never itself). Even any isn’t assignable to never.

If you use assert, The code will run successful too.

const promise = Promise.resolve(true);

const bar = () : Promise<string> => promise.then((condition) => {
    if (condition) {
        return 'foo';
    }
    return <Promise<string>>Promise.reject('bar');
});

I guess the answer is this

Thanks for your answer, that makes sense, but it seems like Promise<never> is not a correct or useful return type for Promise.reject.

Here, for what it's worth is what I came up with - you could definitely use this instead of the default one in lib.es6.d (or feel free to create a PR with it or a better version):

abstract class Promisish<T, E = Error> {
    static reject<T = never, E = Error>(value: E): Promisish<T, E> {
        return {} as Promisish<T, E>
    }
    static resolve<T, E = Error>(value: T): Promisish<T, E> {
        return {} as Promisish<T, E>
    }
    abstract then<R, E2 = E>(f: (r: T) => R): Promisish<R, E>
    abstract catch<R, E2 = E>(f: (e: E) => R): Promisish<T | R, E2>
    abstract finally<E2 = E>(f: () => never): Promisish<T, E2>
}

Another instance of this bug (playground):

Promise.resolve(1).then(x => { // <-- Error here
    if (Math.random() > 0.5) {
        return Promise.resolve('foo');
    } else {
        return x;
    }
});

Error: Argument of type '(x: number) => number | Promise<string>' is not assignable to parameter of type '(value: number) => string | PromiseLike<string>'.

Looks as if a mix of promise and non-promise return values confuse typescript (playground):

interface A {
    isA: true;
}
interface B {
    isA: false;
}

function x(): Promise<A | B> {
    return Promise.resolve(true).then(wantA => { // <= Error here
        if (wantA) {
            return { isA: true as true};
        }
        return Promise.resolve().then(
            () => ({ isA: false as false });
        )
    }); 
}
Argument of type '(wantA: boolean) => Promise<{ isA: false; }> | { isA: true; }' is not assignable to parameter of type '(value: boolean) => { isA: false; } | PromiseLike<{ isA: false; }>'.
  Type 'Promise<{ isA: false; }> | { isA: true; }' is not assignable to type '{ isA: false; } | PromiseLike<{ isA: false; }>'.
    Type '{ isA: true; }' is not assignable to type '{ isA: false; } | PromiseLike<{ isA: false; }>'.
      Type '{ isA: true; }' is not assignable to type 'PromiseLike<{ isA: false; }>'.
        Property 'then' is missing in type '{ isA: true; }'.

Edit: There is a simple way to calm typescript: Extract the body into a separate function and have it return the combined type A | B | Promise<A | B> (playground).

Seems to be fixed now

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dlaberge picture dlaberge  Â·  3Comments

weswigham picture weswigham  Â·  3Comments

fwanicka picture fwanicka  Â·  3Comments

MartynasZilinskas picture MartynasZilinskas  Â·  3Comments

seanzer picture seanzer  Â·  3Comments