- Get link
- X
- Other Apps
- Get link
- X
- Other Apps
In Typescript you have the never typehint. With the never typehint you tell typescript this function never exits gracefully. But it seems that the never typehint can be used in different setups.
Basic usage
In Typescript you can use the never typehint to help Typescript detect dead code. For example in this code typescript can not figure out that this code always works:
function throwInvalidError(message: string): any {
throw new Error(message);
}
function doSomething(input: string): void {
// do something with input
}
function doThis(input: number|string): string {
if (typeof input === 'number') {
throwInvalidError("I expect a string but got a number!');
}
doSomething(input);
}
This is a basic example of type guarding as in the if statement typescript knows it has to be a number. If there would be an else statement, it would know it can only be a string. The problem is the doSomething call after it as typescript misses that throwInvalidError never exits gracefully by throwing an error, so it assumes that input could be a number again. So if we fix this with a never typehint this code will compile just fine:
function throwInvalidError(message: string): never {
throw new Error(message);
}
function doSomething(input: string): void {
// do something with input
}
function doThis(input: number|string): string {
if (typeof input === 'number') {
throwInvalidError("I expect a string but got a number!');
}
doSomething(input);
}
Typescript sees the never typehint and knows that input has to be a string when calling doSomething(). The other application of typehinting never is a function that runs in an endless loop or asynchronous loop:
function runForever(): never {
while (true) {
}
}
asycn function runForeverAsync(): Promise<never> {
while (true) {
elm.innerText = (new Date).getTime();
await delay(100);
}
}
runForeverAsync().then((result) => doSomething()); // this throws warning that doSomething is never being called
In all my years I have never seen anyone write a runForever method intentionally in Javascript, so I though the usage of never is limited, until I figured out it can be used in different contexts.
Other usages
A feature often missed is defining that a function can not have arguments:
type fn = (...args: never[]) => void;
type globalFn = (this: never, ...args: any[]) => any;
let f: globalFn = () => {}
f.call({}); // this throws an error for trying to rebind this.
The only allowed value of args is an empty array, so fn is a typehint for a function that returns nothing and has no arguments. The globalFn typehint defines that this function can not be used with function.call(object) to bind a this pointer and allows any return type or argument list.
The keyof problem
In typescript you can typehint a variable to be a key of an interface/object:
const value = {
a: 12,
b: 13
}
interface Example {
c: number;
d: string;
}
valueKey: keyof typeof value; // so valueKey can be 'a' or 'b'
exampleKey: keyof Example; // so exampleKey can be 'c' or 'd'
const implicitNumberType = value[valueKey]; // typescript knows this is a number
const exampleInstance: Example;
const implicitUnionType = exampleInstance[exampleKey]; // typescript knows implicitUnionType is string or number.
And the problem I ran into was similar like this:
interface Example {
a: number;
b: string;
c: number;
d: string;
e: number;
f: boolean;
}
function getString(example: Example, key: keyof Example): string {
return example[key];
}
This throws an error as key can also be 'a', 'c' or 'e' which returns a number and 'f' returns a boolean. We could use string literals to fix this:
interface Example {
a: number;
b: string;
c: number;
d: string;
e: number;
f: boolean;
}
function getString(example: Example, key: 'b' | 'd'): string {
return example[key];
}
This will fix the typescript error, but it makes it a bit awkward to update. Adding a new property to Example interface of type 'string' will not update getString typehints automatically. So how to fix this automatically? Considering we are talking about 'never' you might see how to fix this. And we also use generics in our type definition:
type KeyOfType<T, V> = keyof {
[P in keyof T as T[P] extends V? P: never]: any
}
interface Example {
a: number;
b: string;
c: number;
d: string;
e: number;
f: boolean;
}
function getString(example: Example, key: KeyOfType<Example, string>): string {
return example[key];
}
We add a new type definition with generics called KeyOfType that looks very intimidating. In our getString we use it instead of using keyof and our typescript will compile! And if you try to call getString, for example getString(example, 'a') you will get an error that 'a' is not a valid value!
So let's deduce how this KeyOfType works. If we would fill in Exampe and string to get rid of the generics we get this:
So let's deduce how this KeyOfType works. If we would fill in Exampe and string to get rid of the generics we get this:
type KeyOfType = keyof {
[P in keyof Example as Example[p] extends string? P : never]: any
}
In a nutshell the typehint becomes this depending on the type found in Example:
type KeyOfType = keyof {
a: never;
b: string;
c: never;
d: string;
e: never;
f: never;
keyof will remove the never typehints and you end up with the correct typehint!
Conclusion
So Typescript has a very extensive typehint system. That is not a suprise considering Javascript is a very dynamic programming language. Type definitions like KeyOfType remind me a lot of C and C++ header files with tons of macro definitions so you can compile it with any compiler. They are very powerful, but also hard to read for fellow programmers, so use them in case they really benefit other programmers. But for me it was fun to see how I can use the never typehint in Typescript with an actual use case of it.
Comments
Post a Comment