3

Could you help me understand why TS compiler doesn't complain about this statement?

Promise.reject().catch(() => 5)

If checking the definition of the handler function inside the catch we can see the piece of code as follows:

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

    catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
}

It assumes that the handler function returns TResult | PromiseLike<TResult> where TResult = never. And then in my example a handler function returns 5 which is a number. So I could assume that 5 is never | PromiseLike<never>. But it is not the case. A number doesn't have .then function so it's not a PromiseLike. Thanks!

5
  • 3
    ... Well no, TResult is inferred number, so it's actually number | PromiseLike<number>, and 5 is assignable to that type. If you have an editor with language support, you should hover over the catch call to see what the types are. Commented May 17, 2023 at 20:00
  • 3
    TResult just defaults to never according to that signature. It’s not constrained to it. It’s the difference between = and extends. Commented May 17, 2023 at 20:00
  • 1
    Relevant: typescript generic type with equal operator means? Commented May 17, 2023 at 20:01
  • @zenly Thanks! Didn't know if hovering in IDE it will show the type by inferring Commented May 17, 2023 at 20:13
  • @jcalz Thanks for sharing the difference between extends and =. I wasn't aware of it Commented May 17, 2023 at 20:14

2 Answers 2

1

TypeScript doesn't complain because the type parameter TResult in that definition of catch() method just defaults to never. The type parameter TResult is not constrained to never in any way.

Note that the = operator inside a type parameter declaration is a default type argument, while the extends operator inside a type parameter declaration is a type parameter constraint. In general you can have neither or either or both. A type parameter declaration on a call signature like <T extends C = D>(⋯) => ⋯ means that T must be assignable to C, and if T cannot be inferred, it will default to D (which must also be assignable to the constraint C). If you leave out C then it is equivalent to specifying it as the unknown type, and if you leave out D then it is equivalent to specifying it as C. So <TResult = never> is equivalent to <TResult extends unknown = never>, meaning it defaults to never but is not constrained at all.

So: TResult will only fall back to never in the event that it cannot be inferred from the input, such as if the onrejected argument is not supplied. But you did supply an onrejected argument of () => 5. And thus TypeScript was able to infer the TResult type argument as number, because () => 5 is a valid ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null. You can verify this by hovering over the catch call in an IntelliSense-enabled IDE:

Promise.reject().catch(() => 5)
// (method) Promise<never>.catch<number>(
//    onrejected?: ((reason: any) => number | PromiseLike<number>) | null | undefined
// ): Promise<number>

So everything's working as expected and intended here.

Sign up to request clarification or add additional context in comments.

Comments

0

Since the callback function returns a value of 5, the new promise created by catch() is resolved with that value. Meaning, the new promise created by catch() is resolved with the value of 5 because the callback function returns that value.

6 Comments

How does this address the question, which is "why doesn't the TS compiler complain about this when number is not assignable to PromiseLike<never>?"
TypeScript should still complain because the catch() method is expecting a PromiseLike object as its return value. But the callback function returns the value 5, which is a valid value for a promise to resolve to, meaning the new promise created by catch() is resolved with that value. In TypeScript, a promise can be of any type, so a promise that resolves to the value 5 is perfectly valid.
I don't understand what you mean by "TypeScript should still complain". It doesn't complain. So do you really mean that it should? If so, why doesn't it? If not, why say that?
I meant number is not assignable to PromiseLike<never>, so still we would expect that TS would complain, but it didn't, why it didn't? because the call back returns a integer value, which doesn't matter which type is it, still is perfectly resolvable :) I need some one to edit me.
Either I'm not understanding what you're saying, or what you are saying is incorrect or not relevant. TypeScript doesn't have higher-order reasoning abilities that would enable it to ignore a detected type error because it's "resolvable". Any answer to this question should address the misconception that <TResult = never> implies that TResult will be never. Since your answer makes no mention of this whatsoever, and neither do your comment replies, I'm worried that you're just saying "it's not an error because it's fine", which doesn't really engage with the question as asked.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.