as never
is very occasionally needed in TypeScript. Let's look at an example where it's necessary.
Let's imagine we want to format some input based on its typeof
. We first create a formatters
object that maps typeof
to a formatting function:
const formatters = {
string: (input: string) => input.toUpperCase(),
number: (input: number) => input.toFixed(2),
boolean: (input: boolean) => (input ? "true" : "false"),
};
Next, we create a format
function that takes an input of string | number | boolean
and formats it based on its typeof
.
const format = (input: string | number | boolean) => {
// We need to cast here because TypeScript isn't quite smart
// enough to know that `typeof input` can only be
// 'string' | 'number' | 'boolean'
const inputType = typeof input as
| "string"
| "number"
| "boolean";
const formatter = formatters[inputType];
return formatter(input);Argument of type 'string | number | boolean' is not assignable to parameter of type 'never'.
Type 'string' is not assignable to type 'never'.2345
Argument of type 'string | number | boolean' is not assignable to parameter of type 'never'.
Type 'string' is not assignable to type 'never'.};
But there's a strange error:
Type 'string' is not assignable to type 'never'.
What's going on here?
#
Let's take a deeper look at the type of formatter
inside our format
function:
const format = (input: string | number | boolean) => {
const inputType = typeof input as
| "string"
| "number"
| "boolean";
const formatter = formatters[inputType];
return formatter(input);
};
As you can see, it resolves to a union of functions, each with a different parameter. One function takes a string
, another a number
, and the last a boolean
.
How could we possibly call this function with a string
and a number
at the same time? We can't.
So, the function actually resolves to:
type Func = (input: never) => string;
#
You might be thinking, "Shouldn't the parameters resolve to a union of string | number | boolean
?"
This doesn't work, because calling formatters.string
with a number
is unsafe. Calling formatters.boolean
with a number
is unsafe.
So, never
is the only type that makes sense.
#
We happen to know that the logic of this function is sound. We know that formatters[inputType]
will resolve to the correct type.
So, we can use an as never
:
const format = (input: string | number | boolean) => {
const inputType = typeof input as
| "string"
| "number"
| "boolean";
const formatter = formatters[inputType];
return formatter(input as never);
};
This forces TypeScript to consider input
as the type of never
- which is, of course, assignable to formatter
's parameter of never
.
#
Amazingly, any
doesn't work here:
const format = (input: string | number | boolean) => {
const inputType = typeof input as
| "string"
| "number"
| "boolean";
const formatter = formatters[inputType];
return formatter(input as any);Argument of type 'any' is not assignable to parameter of type 'never'.2345
Argument of type 'any' is not assignable to parameter of type 'never'.};
It results in a horrendous error:
Argument of type 'any' is not assignable to parameter of type 'never'.
So, as never
is the only way to go here.