Перейти к основному содержимому

Обработка ошибок в TypeScript

Разработчику Архитектору

Дальше: Паттерны · TypeScript и Node.js · Асинхронность · Справочник — распространённые паттерны

Сначала — общая теория

Механика throw в TypeScript та же, что в JavaScript. Зато типы помогают описать ошибки явно: ожидаемые сбои удобно класть в Result (discriminated union успех/ошибка), инфраструктурные — обрабатывать в catch с unknown. Так важные ветки обработки видны в контракте, без потери в общем try/catch. Удобный порядок — асинхронность, эта статья, паттерны, TypeScript и Node.js.

unknown в catch — Типы данных и типизация в TypeScript, Асинхронное программирование в TypeScript. Валидация — Рекомендации по разработке на TypeScript.


Два семейства ошибок

ТипПримерыКак моделировать
Ожидаемыеневалидный email, ресурс не найденResult, union, HTTP 4xx
Неожиданныедиск, OOM, багthrow, HTTP 5xx, лог + alert
// Ожидаемая — часть контракта
function parseAge(input: string): Result<number, "nan" | "negative"> { /* ... */ }

// Неожиданная — прерывает поток
async function readSecret(): Promise<string> {
const data = await fs.readFile("/secret", "utf8");
return data;
}

Result<T, E>

Код ITЗагрузка примера кода…

Разбор:

  • Вызывающий обязан проверить ok — иначе нет доступа к value.
  • E можно сузить до union литералов — автодополнение в if (!r.ok).

Комбинирование Result

function mapResult<T, U, E>(
r: Result<T, E>,
fn: (v: T) => U,
): Result<U, E> {
return r.ok ? { ok: true, value: fn(r.value) } : r;
}

В библиотеках: neverthrow, ts-results — по желанию команды.


Option<T> (отсутствие значения)

Код ITЗагрузка примера кода…

Разбор:


try/catch и unknown

Код ITЗагрузка примера кода…

Разбор:

  • Не используйте catch (e: any).
  • instanceof Error не покрывает все throw в JS.

Кастомные классы ошибок

class AppError extends Error {
constructor(
message: string,
readonly code: "NOT_FOUND" | "FORBIDDEN" | "VALIDATION",
) {
super(message);
this.name = "AppError";
}
}

function isAppError(e: unknown): e is AppError {
return e instanceof AppError;
}

HTTP-маппинг в Node:

function toStatus(e: AppError): number {
switch (e.code) {
case "NOT_FOUND":
return 404;
case "FORBIDDEN":
return 403;
case "VALIDATION":
return 400;
}
}

Разбор:

  • Класс удобен для центрального error middleware.
  • Для чистых функций предпочтительнее Result без throw.

Ошибки в async-цепочках

async function fetchUser(id: string): Promise<Result<User, "http" | "invalid">> {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) return { ok: false, error: "http" };
const raw: unknown = await res.json();
if (!isUser(raw)) return { ok: false, error: "invalid" };
return { ok: true, value: raw };
} catch {
return { ok: false, error: "http" };
}
}

Promise reject и возврат Result — выберите один стиль на слой; смешение усложняет код.


Единый формат API-ошибки

Код ITЗагрузка примера кода…

Разбор:

  • Frontend типизирует ApiErrorBody и показывает details у полей формы.

Zod на границе

Код ITЗагрузка примера кода…

Подробнее — Рекомендации по разработке на TypeScript.


throw и Result — шпаргалка

СитуацияРекомендация
Валидация формы / DTOResult или Zod safeParse
Ресурс не найден в сервисеResult или AppError NOT_FOUND
Падение диска / сеть без обработкиthrow + middleware
React load dataunion LoadStateTypeScript и React

Частые ошибки

ОшибкаЧто делать
catch (e: any)unknown + narrowing
Игнорировать !r.okвключить ESLint на floating promises / strict
throw для валидации вездеResult на границе domain
Один Error на всёunion кодов или классы
Типы ошибок не в APIApiErrorBody

Практика

  1. Перепишите парсер числа на Result<number, "nan" | "negative">.
  2. Добавьте findById с Option или Result.
  3. Сделайте AppError + маппинг в HTTP-статус.
  4. В React отобразите LoadState error — TypeScript и React.
  5. Оберните POST body в Zod safeParse (опционально).

Смежные статьи