TypeScript никогда не вводить вывод

Может кто-нибудь объяснить мне, почему приведен следующий код:

let f = () => {
    throw new Error("Should never get here");
}

let g = function() { 
    throw new Error("Should never get here");
}

function h() { 
    throw new Error("Should never get here");
}

Выводятся следующие типы:

  • f () => never
  • g () => never
  • h () => void

Я бы ожидал, что тип h будет () => never.

Спасибо!

Ответ 1

Отличный вопрос. Разница в том, что f и g являются функциональными выражениями, где h является объявлением функции. Когда функция throw - только, она получает тип never, если это выражение, и void, если это объявление.

Несомненно, этот абзац на самом деле не помогает. Почему существует разница в поведении между выражением функции и объявлением функции? Давайте посмотрим на некоторые встречные примеры в каждом случае.

Плохая идея # 1: Сделать выражения функции throw return void

Рассмотрим некоторый код:

function iif(value: boolean, whenTrue: () => number, whenFalse: () => number): number {
    return value ? whenTrue() : whenFalse();
}
let x = iif(2 > 3,
  () => { throw new Error("haven't implemented backwards-day logic yet"); },
  () => 14);

Этот код в порядке? Должен быть! Обычно писать функцию throw ing, когда мы считаем, что функция не должна вызываться или ее нужно вызывать только в случаях ошибок. Если тип выражения функции был void, вызов iif был бы отклонен.

Итак, из этого примера видно, что выражения функций, которые только throw должны возвращать never, а не void. И действительно, это должно быть нашим предположением по умолчанию, потому что эти функции соответствуют определению never (в правильно напечатанной программе значение типа never не может быть соблюдено).

Плохая идея # 2: Сделать декларации функции throwing never

Прочитав предыдущий раздел, вы должны сказать "Отлично, почему не все функции бросания возвращаются never, то?"

Короткий ответ заключается в том, что это стало большим нарушением. Там много кода (особенно код, предшествующий ключевому слову abstract), который выглядит так:

class Base {
    overrideMe() {
        throw new Error("You forgot to override me!");
    }
}

class Derived extends Base {
    overrideMe() {
        // Code that actually returns here
    }
}

Но функция, возвращающая void, не может быть заменена функцией, возвращающей never (помните, что в правильно напечатанной программе значения never не могут быть соблюдены), поэтому сделать Base#overrideMe return never предотвращает Derived от реализации реализации этого метода не never.

И вообще, в то время как выражения функций, которые всегда бросаются, часто существуют как разновидности заполнителей для Debug.fail, объявления функций, которые всегда бросаются, очень редки. Выражения часто получают псевдоним или игнорируются, тогда как объявления статичны. Объявление функции, что throw сегодня, скорее всего, сделает что-то полезное завтра; в отсутствие аннотации типа возврата более безопасная вещь void (т.е. еще не смотри этот тип возврата), а не never (т.е. эта функция является черной дырой, которая будет потреблять текущий стек выполнения).