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

Обработка ошибок в JavaScript строится на двух фундаментальных механизмах: синхронном и асинхронном. Синхронные ошибки возникают во время выполнения кода, а асинхронные — внутри промисов, таймеров или асинхронных функций. Основной целью обработки ошибок является предотвращение падения программы и корректная реакция на непредвиденные ситуации.


Объект Error

Все ошибки в JavaScript представляют собой объекты, производные от базового класса Error.

Основные свойства объектов ошибок:

  • name — тип ошибки (например, TypeError, ReferenceError).
  • message — текстовое описание ошибки.
  • stack — стек вызовов, полезный для отладки.

Пример создания ошибки вручную:

throw new Error('Неверный формат данных');

Кроме базового класса Error, есть встроенные специализированные классы ошибок:

  • SyntaxError — ошибка синтаксиса.
  • ReferenceError — обращение к несуществующей переменной.
  • TypeError — некорректный тип данных.
  • RangeError — выход за предел допустимого диапазона.
  • URIError — ошибки функций работы с URI.

Конструкция try…catch

Основной инструмент для перехвата ошибок — конструкция try...catch. Она позволяет выполнять код в блоке try и перехватывать исключения в блоке catch.

try {
    let result = riskyFunction();
    console.log(result);
} catch (error) {
    console.error('Произошла ошибка:', error.message);
}

Ключевые моменты:

  • catch получает объект ошибки.
  • Можно использовать catch без параметра в современных версиях JS, но чаще требуется доступ к объекту ошибки.
  • В блоке catch допускается любая логика обработки ошибки, включая повторную генерацию исключения с throw.

Блок finally

Блок finally выполняется всегда, независимо от того, произошла ошибка или нет. Он полезен для освобождения ресурсов или завершения операций, которые должны выполниться обязательно.

try {
    openConnection();
    processData();
} catch (error) {
    console.error(error);
} finally {
    closeConnection();
}

Генерация собственных ошибок

Создание пользовательских ошибок позволяет контролировать поведение приложения в специфических ситуациях.

function validateAge(age) {
    if (age < 0 || age > 150) {
        throw new RangeError('Возраст вне допустимого диапазона');
    }
}

Можно создавать собственные классы ошибок, наследуя Error:

class ValidationError extends Error {
    constructor(message) {
        super(message);
        this.name = 'ValidationError';
    }
}

throw new ValidationError('Неверное значение поля');

Асинхронные ошибки

Асинхронные операции требуют отдельного подхода. Промисы и async/await позволяют обрабатывать ошибки следующим образом:

Через .catch() у промиса:

fetch('/data.json')
    .then(response => response.json())
    .catch(error => console.error('Ошибка запроса:', error));

Через try…catch в async-функции:

async function fetchData() {
    try {
        let response = await fetch('/data.json');
        let data = await response.json();
        console.log(data);
    } catch (error) {
        console.error('Ошибка при загрузке данных:', error);
    }
}

Глобальная обработка ошибок

Для перехвата необработанных исключений существуют глобальные обработчики:

  • В браузере: window.onerror и window.onunhandledrejection.
  • В Node.js: process.on('uncaughtException') и process.on('unhandledRejection').
window.addEventListener('error', event => {
    console.error('Глобальная ошибка:', event.message);
});

window.addEventListener('unhandledrejection', event => {
    console.error('Необработанный промис:', event.reason);
});

Эти обработчики полезны для логирования ошибок и предотвращения полного сбоя приложения.


Рекомендации по обработке ошибок

  • Не использовать try...catch для контролируемого потока выполнения. Он предназначен только для непредвиденных ситуаций.
  • Обрабатывать ошибки там, где можно принять корректное решение.
  • Предпочтение отдавать специфическим типам ошибок (TypeError, RangeError) для лучшей диагностики.
  • Для асинхронного кода использовать try...catch вместе с async/await или .catch() для промисов.
  • Логировать ошибки с контекстом выполнения, чтобы ускорить отладку.

Перевыбрасывание ошибок

Иногда обработка ошибки частичная, и её нужно передать выше по стеку вызовов. Это делается через throw внутри блока catch:

try {
    processData();
} catch (error) {
    logError(error);
    throw error; // передача ошибки дальше
}

Перевыбрасывание позволяет комбинировать локальную обработку и глобальный контроль ошибок.


Принципы устойчивого кода

  • Все потенциально опасные операции (работа с сетью, файловой системой, JSON) следует оборачивать в обработку ошибок.
  • Использование собственных классов ошибок улучшает читаемость и поддержку кода.
  • Асинхронный код должен гарантированно обрабатывать ошибки, чтобы избежать “зависших” промисов.

Обработка ошибок является фундаментальной частью устойчивого и поддерживаемого JavaScript-кода, обеспечивая безопасность и предсказуемость работы приложений.