The primitive type never in Typescript
Recently I read Typescript documents more carefully to know them deeply. So this article is one of the things I haven't known well about never
in Typescript.
never
The type never
means that nothing happens. The example is always throwing an exception error, or a type guard cannot work.
never vs void
let v1: void = null
let v2: void = undefined
let v3: never = null // error
let v4: never = undefined // error
In Typescript, the type void can be assignable for undefined
or null
. But never
cannot be assignable.
Always throw an error
function fn1(): never {
throw new Error('never return anything')
}
As you can see above, if a function always throws an error, you can define the function's return type as never
. So the difference between never
and void
is the type void
's return type is undefined
. But never
does not return anything.
Check exhaustive validations
For example, when we use a Union or an Enum type, you have to make sure that all conditions return something than never
. If it's not, the compiler will complain about it.
enum AppleLaptops {
MACBOOK_PRO,
MACBOOK_AIR,
MACBOOK
}
function unhandledCaseError(v): never {
throw new Error(`${v} case handler is undefined.`)
}
// Case 1
function returnStringFromEnum(v: AppleLaptops): string {
switch (v) {
case AppleLaptops.MACBOOK_PRO:
return 'MACBOOK PRO'
case AppleLaptops.MACBOOK_AIR:
return 'MACBOOK AIR'
default:
return ''
}
}
// Case 2
type AppleWatches = 'SERIES' | 'ULTRA' | 'SE'
function returnStringFromUnion(v: AppleWatches): string {
if (v === 'SERIES') { return 'SERIES' }
if (v === 'ULTRA') { return 'ULTRA' }
return unhandledCaseError(v)
}
In the case above, Case 1 compiled successfully because the default case statement returns an empty string, which is not never
. The problem that we faced is we were missing handling the additional case(MACBOOK) in the enumeration. Let's see the second case. In Case 2, it returns the unhandledCaseError
function result, which returns never
. So the compiler raised an error because the SE
case was not handled correctly. Using never
helps you to identify a missing piece of the exhaustive check.
A subtype of every type
The type can never be a subtype of every type. But it cannot be assigned to any type. In other words, the type never
is a universal type that replaces any typed value.
Suppose you want to know more about this. Please refer to this link.
function returnNever() {
throw new Error('return never')
}
let neverType1: never = returnNever()
let text: string = neverType1 // it works!
let neverType2: never = 'test' // error!
Mapped Type
The type never
is proper when you create a custom-mapped type. For example, let's assume you want to make a custom-type NoNullable
, which filters undefined
or null
from an origin type. So we can create the type using a conditional mapped type like the one below.
type NoNullable<T> = T extends undefined | null ? never : T;
function printValue<T>(value: NoNullable<T>): void {
console.log(`the input is ${value}.`)
}
console.log(printValue('test'))
console.log(printValue(null)) // raised an error
Through this article, I explained how never
can be used. I think that the Mapped type example can be useful for my future work.