Can someone please explain me why given the following code:
let f = () => { throw new Error("Should never get here"); } let g = function() { throw new Error("Should never get here"); } function h() { throw new Error("Should never get here"); }
The following types are inferred:
f
is () => never
g
is () => never
h
is () => void
I would expect the type of h
to be () => never
as well.
Thanks!
TypeScript introduced a new type never , which indicates the values that will never occur. The never type is used when you are sure that something is never going to occur. For example, you write a function which will not return to its end point or always throws an exception.
TypeScript infers types of variables when there is no explicit information available in the form of type annotations. Types are inferred by TypeScript compiler when: Variables are initialized. Default values are set for parameters.
push('one'); We declared an empty array and it got assigned a type of never[] . This type represents an array that will never contain any elements (will always be empty). To solve the error, explicitly type the empty array. index.ts.
The error "Property does not exist on type 'never'" occurs when we try to access a property on a value of type never or when TypeScript gets confused when analyzing our code. To solve the error, use square brackets to access the property, e.g. employee['salary'] .
Great question. The difference is that f
and g
are function expressions, where h
is a function declaration. When a function is throw
-only, it gets the type never
if it's an expression, and void
if it's a declaration.
Surely the above paragraph doesn't actually help. Why is there a difference in behavior between a function expression and a function declaration? Let's look at some counter-examples in each case.
void
Consider some code:
function iif(value: boolean, whenTrue: () => number, whenFalse: () => number): number { return value ? whenTrue() : whenFalse(); } let x = iif(2 > 3, () => { throw new Error("haven't implemented backwards-day logic yet"); }, () => 14);
Is this code OK? It should be! It's common to write a throw
ing function when we believe the function shouldn't be called, or should only be called in error cases. If the type of the function expression were void
, though, the call to iif
would be rejected.
So it's clear from this example that function expressions which only throw
ought to return never
, not void
. And really this should be our default assumption, because these functions fit the definition of never
(in a correctly-typed program, a value of type never
cannot be observed).
never
After reading the prior section, you should be saying "Great, why don't all throwing functions return never
, then?"
The short answer is it turned out to be a big breaking change to do so. There's a lot of code out there (especially code predating the abstract
keyword) that looks like this
class Base { overrideMe() { throw new Error("You forgot to override me!"); } } class Derived extends Base { overrideMe() { // Code that actually returns here } }
But a function returning void
can't be substituted for a function returning never
(remember, in a correctly-typed program, never
values cannot be observed), so making Base#overrideMe
return never
prevents Derived
from providing any non-never
implementation of that method.
And generally, while function expressions that always throw often exist as sort of placeholders for Debug.fail
, function declarations that always throw are very rare. Expressions frequently get aliased or ignored, whereas declarations are static. A function declaration that throw
s today is actually likely to do something useful tomorrow; in the absence of a return type annotation, the safer thing to provide is void
(i.e. don't look at this return type yet) rather than never
(i.e. this function is a black hole that will eat the current execution stack).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With