Menu

Тип never в TypeScript: исчерпывающие проверки и void

never это тип без значений. Это тип возврата функций, которые никогда не завершаются, тип, который остаётся, когда сужение исключило все варианты, и основа исчерпывающих проверок switch. Откуда он берётся и чем отличается от void.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

never это тип без значений. Функция с типом возврата never никогда не возвращает управление нормально: она выбрасывает ошибку или работает вечно. Переменная типа never отмечает код, который не может выполниться, и именно это делает возможными исчерпывающие проверки.

return fail(...) компилируется в функции, которая возвращает number, потому что never присваивается любому типу: значение типа never на самом деле никогда не появится.

Функции, которые никогда не возвращают управление

Два вида функций никогда не доходят до конца: та, что всегда выбрасывает ошибку, и та, что содержит бесконечный цикл. Компилятор проверяет это утверждение. Функция с аннотацией never, конец которой достижим, это ошибка TS2534, A function returning 'never' cannot have a reachable end point.

function fail(message: string): never {
  throw new Error(message);
}

function runForever(): never {
  while (true) {
    // poll, serve requests...
  }
}

Вывод типа зависит от синтаксиса. Для объявления функции, которое только выбрасывает ошибку, выводится возврат void, а для стрелочной функции или функционального выражения, которые только выбрасывают ошибку, выводится never:

function f1() { throw new Error("x"); }       // () => void
const f2 = () => { throw new Error("x"); };   // () => never

Сужение считает вызов тупиком, только если у вызываемого имени есть явный тип, возвращающий never: объявление функции с аннотацией : never, как fail выше, или переменная с аннотацией типа, const fail: (m: string) => never = (m) => { throw new Error(m); }. Выведенный never не считается, как и const fail = (m: string): never => ..., где аннотирована только стрелочная функция, а не переменная.

never и void

voidnever
Функция завершаетсяданет (выбрасывает ошибку или зацикливается)
Значение во время выполненияundefinedнет: вызов никогда его не выдаёт
Код после вызовадостижимнедостижим
Присваивается другим типамтолько void, unknown, anyлюбому типу
Типичное использованиеколбэки, обработчики событий, функции с побочными эффектамиfail(), assertNever(), бесконечные циклы

Практическая разница видна при сужении. После if (!user) fail("no user") компилятор знает, что user определён на следующей строке, только если fail возвращает never. С возвратом void он предполагает, что выполнение может продолжиться.

Исчерпывающие проверки через never

Каждый case оператора switch по объединению сужает значение. Когда все члены обработаны, в default остаётся never. Присваивание его переменной never превращает «я обработал все случаи» в то, что проверяет компилятор:

Теперь добавьте в объединение третий член, не добавляя case:

index.ts(18,26): error TS2345: Argument of type '{ kind: "triangle"; base: number; height: number; }' is not assignable to parameter of type 'never'.

Ошибка называет член, который вы забыли. Добавьте case "triangle": return (shape.base * shape.height) / 2;, и код снова компилируется. Вспомогательная функция assertNever это многоразовая форма той же проверки, и её throw важен и во время выполнения: данные из JSON или от старого клиента могут содержать kind, который по типам невозможен. Этот приём лежит в основе размеченных объединений.

Сужение до never

То же самое происходит при любом сужении, а не только в switch. Когда все варианты исключены, переменная получает тип never:

Если позже расширить параметр до string | number | boolean | bigint, строка const nothing: never = x станет ошибкой и укажет на функцию, которую нужно обновить.

never исчезает в объединениях

never это пустое множество значений, поэтому его добавление в объединение ничего не меняет: string | never это просто string. Так условные типы фильтруют объединения. Ветка, которая возвращает never, удаляет этот член:

Встроенные Exclude<T, U> и Extract<T, U> работают именно так. В пересечении наоборот: string & never это never.

Невозможные типы становятся never

Пересечение, которому не может соответствовать ни одно значение, сводится к never:

type A = string & number;                   // never
type B = { kind: "a" } & { kind: "b" };     // never

Чтение свойства значения типа B сообщает причину: Property 'kind' does not exist on type 'never'. The intersection 'B' was reduced to 'never' because property 'kind' has conflicting types in some constituents. Если построенный вами тип оказался never, ищите две части, которые противоречат друг другу.

never, unknown и any

ТипКакие значения содержитКому присваиваетсяЧто принимает
unknownлюбые значениятолько unknown и anyвсё
anyлюбые значениявсему, кроме neverвсё
neverникаких значенийвсемутолько never

unknown это вершина иерархии типов, а never её дно. any вообще не входит в иерархию: он отключает проверки.

Часто задаваемые вопросы

Что такое тип never в TypeScript?

never это тип, у которого нет значений. Ему ничего нельзя присвоить (кроме другого never), а сам он присваивается любому типу. Он появляется как тип возврата функций, которые всегда выбрасывают ошибку или работают бесконечно, как тип переменной после того, как сужение исключило все варианты, и как результат невозможных типов вроде string & number.

Чем never отличается от void?

Функция, возвращающая void, завершается нормально; она просто не возвращает полезного значения (во время выполнения возвращается undefined). Функция, возвращающая never, не завершается вообще: она выбрасывает ошибку или работает вечно. Код после вызова функции never недостижим, и TypeScript учитывает это при сужении.

Как сделать исчерпывающую проверку в TypeScript?

В ветке default оператора switch по объединению присвойте значение переменной типа never или передайте его в функцию assertNever(value: never): never, которая выбрасывает ошибку. Если все варианты обработаны, значение там имеет тип never, и код компилируется. Если какой-то вариант пропущен, компилятор сообщает, что пропущенный член нельзя присвоить never.

Почему мой тип стал never?

Обычно потому, что TypeScript исключил все варианты сужением (например, после проверок typeof x === "string" и typeof x === "number" для string | number), или потому, что пересечение невозможно: string & number или два объектных типа, у общего свойства которых конфликтующие литеральные типы. Наведите курсор на тип в редакторе, чтобы увидеть, какой шаг его создал.

Что означает "is not assignable to type never"?

Код попытался поместить настоящее значение туда, где разрешён только never. В исчерпывающей проверке это значит, что какой-то член объединения не обработан. В других местах это часто значит, что массив выведен как never[] или пересечение схлопнулось в never; добавьте аннотацию или исправьте конфликтующие типы.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ