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

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

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

Начать курс

Обработка ошибок в TypeScript: от try/catch к надежному коду

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

Базовый try/catch в TypeScript

Конструкция try/catch — основа обработки ошибок. В TypeScript, в отличие от JavaScript, мы можем типизировать перехваченную ошибку. Однако есть важный нюанс: по умолчанию тип ошибки в блоке catch — unknown, а не Error.

try {
    const data = JSON.parse('невалидный json');
    console.log(data);
} catch (error) {
    // error имеет тип unknown
    if (error instanceof Error) {
        console.error('Произошла ошибка:', error.message);
    } else {
        console.error('Неизвестная ошибка:', error);
    }
}

Почему это важно? В JavaScript можно выбросить что угодно: строку, число, объект. TypeScript защищает нас от небезопасного доступа к свойствам ошибки без проверки. Рекомендуется всегда проверять тип ошибки перед использованием.

Пользовательские классы ошибок

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

class ValidationError extends Error {
    constructor(
        public field: string,
        message: string
    ) {
        super(message);
        this.name = 'ValidationError';
    }
}

class NetworkError extends Error {
    constructor(
        public statusCode: number,
        message: string
    ) {
        super(message);
        this.name = 'NetworkError';
    }
}

function processUserData(data: unknown): void {
    if (!data || typeof data !== 'object') {
        throw new ValidationError('data', 'Некорректные данные');
    }
    
    if (!('email' in data)) {
        throw new ValidationError('email', 'Email обязателен');
    }
}

try {
    processUserData({ name: 'Иван' });
} catch (error) {
    if (error instanceof ValidationError) {
        console.error(`Ошибка валидации поля ${error.field}: ${error.message}`);
    } else if (error instanceof NetworkError) {
        console.error(`Сетевая ошибка ${error.statusCode}: ${error.message}`);
    } else {
        console.error('Неизвестная ошибка:', error);
    }
}

Такой подход делает код самодокументируемым и упрощает отладку. Вы всегда знаете, где и почему произошла ошибка.

Типизация функций с ошибками: never и discriminated unions

TypeScript предоставляет мощные инструменты для работы с ошибками на уровне типов. Используйте тип never для функций, которые всегда выбрасывают исключение, и discriminated unions для возврата ошибок.

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

// Discriminated union для результата
type Result<T> = 
    | { success: true; data: T }
    | { success: false; error: string };

function fetchUser(id: number): Result<{ name: string }> {
    try {
        // Имитация запроса
        if (id <= 0) {
            return { success: false, error: 'Некорректный ID' };
        }
        return { success: true, data: { name: 'Анна' } };
    } catch (error) {
        return { success: false, error: 'Ошибка сети' };
    }
}

const result = fetchUser(1);
if (result.success) {
    console.log('Пользователь:', result.data.name);
} else {
    console.error('Ошибка:', result.error);
}

Паттерн Result позволяет избежать неявных исключений и делает обработку ошибок обязательной — TypeScript заставит вас проверить поле success перед доступом к данным.

Продвинутые паттерны: Either и Try

В функциональном программировании популярны монады Either и Try, которые можно реализовать на TypeScript. Они предоставляют элегантный способ цепочечной обработки ошибок.

class Either<L, R> {
    private constructor(
        private left: L | null,
        private right: R | null
    ) {}

    static left<L, R>(value: L): Either<L, R> {
        return new Either(value, null);
    }

    static right<L, R>(value: R): Either<L, R> {
        return new Either(null, value);
    }

    isLeft(): boolean {
        return this.left !== null;
    }

    isRight(): boolean {
        return this.right !== null;
    }

    map<U>(fn: (value: R) => U): Either<L, U> {
        if (this.isRight()) {
            return Either.right(fn(this.right!));
        }
        return Either.left(this.left!);
    }

    fold<T>(leftFn: (left: L) => T, rightFn: (right: R) => T): T {
        if (this.isLeft()) {
            return leftFn(this.left!);
        }
        return rightFn(this.right!);
    }
}

function divide(a: number, b: number): Either<string, number> {
    if (b === 0) {
        return Either.left('Деление на ноль');
    }
    return Either.right(a / b);
}

const result2 = divide(10, 2)
    .map(x => x * 2)
    .fold(
        error => console.error('Ошибка:', error),
        value => console.log('Результат:', value)
    );

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

Асинхронные ошибки и глобальный обработчик

В асинхронном коде ошибки нужно обрабатывать особенно внимательно. Не забывайте про catch у промисов и используйте глобальные обработчики для непредвиденных ситуаций.

// Глобальный обработчик неперехваченных ошибок
window.addEventListener('unhandledrejection', (event) => {
    console.error('Необработанный промис:', event.reason);
    // Отправка ошибки на сервер
});

// Обработка асинхронных функций
async function fetchData(url: string): Promise<unknown> {
    const response = await fetch(url);
    if (!response.ok) {
        throw new NetworkError(
            response.status,
            `HTTP ошибка: ${response.statusText}`
        );
    }
    return response.json();
}

// Использование с try/catch
async function loadUserProfile(userId: number) {
    try {
        const data = await fetchData(`/api/users/${userId}`);
        console.log('Профиль загружен:', data);
    } catch (error) {
        if (error instanceof NetworkError) {
            showErrorMessage('Сетевая ошибка,                

Блоги

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