The primitive type never in Typescript

development

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.