TypeScript использует try, catch, finally и throw из JavaScript. Особенность TypeScript в переменной catch: при включённом strict она имеет тип unknown, поэтому перед использованием нужно проверить, что было выброшено.
Сторона времени выполнения (как раскручивается стек, какие встроенные типы ошибок существуют) описана в разделе try/catch в JavaScript.
Почему переменная catch имеет тип unknown
JavaScript может выбросить что угодно: Error, строку, число, undefined, объект из библиотеки. TypeScript не может знать, что именно, поэтому при strict (флаг useUnknownInCatchVariables) переменная имеет тип unknown, и её нужно сузить перед использованием:
index.ts(5,34): error TS18046: 'e' is of type 'unknown'.
Без strict e имеет тип any, и тот же код компилируется, а затем ломается во время выполнения, когда выброшена строка: e.message равно undefined, а всё, что глубже, например e.message.length, выбрасывает TypeError. Выкрутиться аннотацией тоже нельзя: catch (e: Error) это ошибка TS1196 (Catch clause variable type annotation must be 'any' or 'unknown' if specified).
Сужение ошибки
instanceof Error покрывает всё, что построено на Error, включая TypeError, SyntaxError, RangeError и ваши собственные подклассы. Для всего остального остаётся преобразовать значение в строку. Небольшая вспомогательная функция сокращает места вызова:
catch, который обрабатывает только часть ошибок, должен выбрасывать остальные дальше: if (!(e instanceof ValidationError)) throw e;. Проглатывание неизвестных ошибок скрывает настоящие баги.
Выбрасывание ошибок
throw принимает любое выражение, и TypeScript его не ограничивает. Всё равно выбрасывайте объекты Error: у них есть трассировка стека, и от этого зависит каждая проверка instanceof Error в кодовой базе.
Функция, которая всегда выбрасывает ошибку, имеет тип возврата never, и TypeScript использует его для сужения после вызова:
Начиная с ES2022 Error принимает второй аргумент с cause, который связывает низкоуровневую ошибку с той, которую выбрасываете вы: throw new Error("could not load settings", { cause: e }). Вызывающий код может прочитать err.cause (тип unknown).
Собственные классы ошибок
Подкласс Error позволяет вызывающему коду различать сбои через instanceof и нести дополнительные данные. Задавайте name, потому что унаследованное значение это "Error", и оно видно в логах и в String(err):
Сначала проверяйте самый конкретный класс, поскольку NotFoundError это ещё и HttpError. Модификатор override здесь необязателен, если не включён noImplicitOverride.
Старые руководства добавляют Object.setPrototypeOf(this, new.target.prototype) в каждый конструктор ошибки. Это было нужно при компиляции классов в функции ES5, где instanceof ломался для подклассов Error. TypeScript 7 больше не поддерживает target: "es5" (он сообщает TS5108: Option 'target=ES5' has been removed), а с ES2015 и новее нативный class ... extends Error работает, как показано выше.
Приём Result
TypeScript не отслеживает, какие ошибки может выбросить функция, поэтому ничто не напоминает вызывающему коду их обработать. Для ожидаемых сбоев (неверный ввод, отсутствующая запись) возврат значения, которое говорит «успех или неудача», вводит ошибку в систему типов:
Чтение r.value до проверки r.ok это ошибка компиляции, и в этом весь смысл. Используйте исключения для неожиданного (баги, оборванное соединение), а значения Result для исходов, о которых должен принять решение вызывающий код.
Ошибки в асинхронном коде
Отклонённый промис становится выброшенной ошибкой в месте await, поэтому try/catch вокруг await работает так же, и переменная снова имеет тип unknown. Единственное отличие это .catch() у промиса: параметр его колбэка типизирован как any, а не unknown, так что аннотируйте его сами (.catch((e: unknown) => ...)). Примеры см. в разделе async/await.
Частые ошибки
- Читать
e.messageбез сужения. Приstrictэто не компилируется; безstrictломается, когда выброшено неError. - Ловить всё и продолжать. Обрабатывайте ожидаемые ошибки, а остальные выбрасывайте дальше.
- Забыть
nameу собственных ошибок. Тогда в логах у всех будетError. - Выбрасывать строки. У
throw "failed"нет трассировки стека, и он не проходит проверкиinstanceof Error.
Часто задаваемые вопросы
Какой тип у ошибки в блоке catch в TypeScript?
unknown при включённом strict (флаг useUnknownInCatchVariables), иначе any. JavaScript может выбросить любое значение, а не только объекты Error, поэтому TypeScript заставляет проверять. Сузьте его через if (e instanceof Error), прежде чем читать e.message.
Можно ли типизировать переменную catch как Error в TypeScript?
Нет. catch (e: Error) это ошибка TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified. Разрешены только unknown и any, потому что ничто не гарантирует, что именно было выброшено. Вместо этого сужайте тип внутри блока.
Как выбросить ошибку в TypeScript?
Так же, как в JavaScript: throw new Error("message") или экземпляр встроенного либо собственного подкласса вроде new RangeError(...). TypeScript позволяет выбрасывать любое значение, но объекты Error сохраняют трассировку стека, и с ними работают проверки instanceof Error.
Как создать собственный класс ошибки в TypeScript?
Расширьте Error, вызовите super(message, options) и задайте name: class NotFoundError extends Error { name = "NotFoundError"; }. Дополнительные поля задаются в конструкторе. С любой поддерживаемой целью (ES2015 и новее) instanceof NotFoundError работает без исправления прототипа.
Есть ли в TypeScript проверяемые исключения или конструкция throws?
Нет. Тип функции ничего не говорит о том, что она может выбросить, и компилятор никогда не проверяет, что ошибки обработаны. Для ошибок, которые вызывающий код должен обработать, возвращайте их как значения с типом Result (размеченное объединение успеха и неудачи), и тогда проверять будет система типов.