Обработка ошибок в TypeScript: продвинутые техники try/catch

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Обработка ошибок в TypeScript: от базового try/catch к промышленным паттернам

Обработка ошибок — это не просто блоки try/catch. В TypeScript, благодаря строгой системе типов, мы можем вывести управление ошибками на принципиально новый уровень. В этой статье мы разберем продвинутые техники, которые помогут вам писать более надежный и предсказуемый код.

Проблема стандартного try/catch в TypeScript

Классический блок try/catch в TypeScript имеет фундаментальную проблему: тип перехватываемой ошибки — unknown (или any в старых версиях). Это означает, что TypeScript не знает, с каким именно объектом мы имеем дело.

try {
  // какой-то опасный код
  const result = JSON.parse('{"data": 123}');
} catch (error) {
  // error имеет тип unknown
  // Нельзя просто вызвать error.message
  console.log(error.message); // Ошибка компиляции!
}

Это вынуждает разработчиков либо игнорировать типы, либо писать громоздкие проверки. Рассмотрим, как решить эту проблему на продвинутом уровне.

1. Создание системы кастомных ошибок с типизацией

Вместо того чтобы бросать обычные Error, создайте иерархию кастомных ошибок. Это позволит TypeScript различать типы ошибок на этапе компиляции.

// Базовый класс для всех ошибок приложения
class AppError extends Error {
  constructor(
    message: string,
    public readonly code: string,
    public readonly statusCode: number = 500
  ) {
    super(message);
    this.name = this.constructor.name;
  }
}

// Конкретные типы ошибок
class ValidationError extends AppError {
  constructor(message: string) {
    super(message, 'VALIDATION_ERROR', 400);
  }
}

class NotFoundError extends AppError {
  constructor(resource: string) {
    super(`${resource} не найден`, 'NOT_FOUND', 404);
  }
}

class DatabaseError extends AppError {
  constructor(message: string) {
    super(message, 'DB_ERROR', 500);
  }
}

// Использование
function findUser(id: string): User {
  if (!id) throw new ValidationError('ID обязателен');
  // ... логика поиска
  if (!user) throw new NotFoundError('Пользователь');
  return user;
}

2. Type guards для обработки ошибок

Чтобы безопасно обрабатывать разные типы ошибок, используйте type guards. Это ключевой паттерн для работы с unknown в catch.

// Type guard для AppError
function isAppError(error: unknown): error is AppError {
  return error instanceof AppError;
}

// Type guard для ValidationError
function isValidationError(error: unknown): error is ValidationError {
  return error instanceof ValidationError;
}

// Продвинутая обработка
try {
  const user = findUser(userId);
} catch (error) {
  if (isValidationError(error)) {
    // TypeScript знает, что error — ValidationError
    console.error(`Ошибка валидации: ${error.message}`);
    return { status: 400, message: error.message };
  }
  
  if (isAppError(error)) {
    // error — AppError
    console.error(`Код ошибки: ${error.code}`);
    return { status: error.statusCode, message: error.message };
  }
  
  // Неизвестная ошибка
  if (error instanceof Error) {
    console.error('Неизвестная ошибка:', error.message);
  }
  
  return { status: 500, message: 'Внутренняя ошибка сервера' };
}

3. Result-паттерн (Either monad)

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

// Определение типов для Result-паттерна
type Success<T> = { success: true; data: T };
type Failure<E> = { success: false; error: E };
type Result<T, E> = Success<T> | Failure<E>;

// Вспомогательные функции
function success<T>(data: T): Success<T> {
  return { success: true, data };
}

function failure<E>(error: E): Failure<E> {
  return { success: false, error };
}

// Использование вместо исключений
function parseJSON<T>(json: string): Result<T, ValidationError> {
  try {
    const data = JSON.parse(json) as T;
    return success(data);
  } catch {
    return failure(new ValidationError('Некорректный JSON'));
  }
}

// Обработка результата
const result = parseJSON<User>('{"name": "John"}');

if (result.success) {
  // TypeScript знает, что result.data — User
  console.log(result.data.name);
} else {
  // TypeScript знает, что result.error — ValidationError
  console.error(result.error.message);
}

4. Библиотека neverthrow для промышленного использования

Для production-проектов рекомендую использовать библиотеку neverthrow. Она предоставляет полноценный Result-тип с цепочками операций.

import { ok, err, Result } from 'neverthrow';

// Функция, возвращающая Result
function divide(a: number, b: number): Result<number, string> {
  if (b === 0) {
    return err('Деление на ноль');
  }
  return ok(a / b);
}

// Цепочка операций
const result = divide(10, 2)
  .map(value => value * 2)          // 10
  .mapErr(error => `Ошибка: ${error}`); // Не выполнится

// Комбинирование результатов
const result1 = divide(10, 2);
const result2 = divide(20, 4);

const combined = Result.combine([result1, result2]);
// combined: Result<[number, number], string>

combined.match(
  ([val1, val2]) => console.log(val1 + val2),  // 5 + 5 = 10
  (error) => console.error(error)
);

5. Продвинутая логика обработки с восстановлением

Иногда нужно не просто перехватить ошибку, но и попытаться восстановить состояние приложения. Реализуем паттерн "Circuit Breaker" с использованием Result.

// Счетчик ошибок для Circuit Breaker
class CircuitBreaker {
  private failureCount = 0;
  private lastFailureTime = 0;
  private readonly threshold = 3;
  private readonly timeout = 30000; // 30 секунд

  async call<T>(fn: () => Promise<T>): Promise<Result<T, string>> {
    if (this.isOpen()) {
      return err('Circuit breaker открыт');
    }

    try {
      const result = await fn();
      this.reset();
      return ok(result);
    } catch (error) {
      this.recordFailure();
      return err(`Ошибка: ${error}`);
    }
  }

  private isOpen                

Блоги

Рекомендаций по книгам