Home > Software engineering >  Expected Promise<string> but got Promise<string|number> in the error message
Expected Promise<string> but got Promise<string|number> in the error message

Time:01-22

Strange type Promise <string|number> in error message

How did the typescript compiler come up with the Type Promise <string|number> ... in the error message below.

Type 'Promise<string | number>' is not assignable to type 'Promise<number>'. Type 'string | number' is not assignable to type 'number'. Type 'string' is not assignable to type 'number'.

The code that generates the error message follows below:

const promise: Promise<number> = Promise.resolve(100).then(n => n.toString());

What I thought would happen

I expected Promise.resolve(100).then(n => n.toString()) to return a Promise<string>. When assigning this to const promise: Promise<number>, I expect typescript to show an error message complaining of a mismatch between Promise<string> and Promise<number>

Instead, typescript somehow adds a string|number union and complains that Promise<string|number> is not assignable to type Promise<number>

CodePudding user response:

The TypeScript call signature for the then() method of Promise objects is equivalent to:

interface Promise<T> {
  then<R = T, E = never>(
    onfulfilled?: ((value: T) => R | PromiseLike<R>) | undefined | null, 
    onrejected?: ((reason: any) => E | PromiseLike<E>) | undefined | null
  ): Promise<R | E>;
}

and has two generic type parameters that the compiler needs to infer when you call it. The first type parameter R corresponds to the "success" return value for the onfulfilled callback, while the second type parameter E corresponds to the "error" return value for the onrejected callback. And then returns a promise with a payload of the union type R | E.

Normally when you write something like

const promise = Promise.resolve(100).then(n => n.toString());

without the onrejected callback, there is no inference site from which to infer E, and the compiler falls back to the default type argument of the impossible never type, and since never is eagerly absorbed by unions, you'd get R | never, or just Promise<R>, corresponding to the type returned from the onfulfilled callback. So in the above call, R is inferred as string, and E fails to infer and falls back to never, and you get Promise<string> out, as expected.

But by annotating the type of promise as Promise<number> in:

const promise: Promise<number> = Promise.resolve(100).then(n => n.toString());

you are getting different behavior. You are telling the compiler that you expect a Promise<R | E> to be Promise<number>, and therefore that E can be contextually typed. So E no longer falls back to never; instead, the compiler tries to choose E so that Promise<R | E> matches Promise<number>... so it chooses number (which you can verify if you ask IntelliSense for the quick info about the then() call):

/* (method) Promise<number>.then<string, number>(
     onfulfilled?: ((value: number) => string | PromiseLike<string>) | null | undefined, 
     onrejected?: ((reason: any) => number | PromiseLike<number>) | null | undefined
): Promise<string | number> */

Of course that doesn't work; R is still inferred as string from the onfullfilled callback, and with E of number, the return type is Promise<string | number> which is not assignable to Promise<number>, and you get the error

// Type 'Promise<string | number>' is not assignable to type 'Promise<number>'.

So that's the explanation.

Backing up: to some extent it's an implementation detail exactly which error message you get when inference fails... in the face of an incompatible type annotation, you know something is going to break, but not necessarily the precise nature of the failure. You didn't expect the compiler to try to use number as context for the inference, and so were surprised to see Promise<string | number> instead of Promise<string>. But since neither of those types would have worked, it's somewhat academic that the error message mentioned one instead of the other.

Playground link to code

  • Related