Обработка ошибок в JavaScript: полное руководство

Зачем обрабатывать ошибки?
Ошибки в программировании неизбежны. Они прерывают нормальный поток выполнения, но одновременно защищают приложение от непредсказуемого поведения. Корректная обработка ошибок:
- улучшает пользовательский опыт за счёт понятных сообщений;
- позволяет продолжить выполнение программы там, где это безопасно;
- упрощает локализацию и отладку проблем во время разработки;
- делает поведение приложения предсказуемым в пограничных случаях.
Важно: не скрывайте ошибки полностью — логируйте их и, при необходимости, отправляйте в систему мониторинга.
Как устроены встроенные объекты ошибок в JavaScript
Ошибки в JavaScript представлены объектами с основными свойствами:
- name — имя типа ошибки (например, ‘SyntaxError’).
- message — текстовое описание ошибки.
- cause — необязательное поле для хранения причины (можно передавать при создании ошибки).
Эти свойства помогают классифицировать и отображать ошибки пользователю или записывать их в логи.
Распространённые типы ошибок в JavaScript
Ниже — список часто встречающихся ошибок и короткое описание, когда они появляются.
SyntaxError
Появляется при синтаксической ошибке в коде — например, пропущена фигурная скобка или скобка.
Когда встречается: при разборе (парсинге) кода. Такие ошибки надо исправлять до выполнения.
ReferenceError
Возникает, когда код пытается обратиться к переменной, которая не определена или недоступна по области видимости.
TypeError
Появляется, когда операция недопустима для данного типа данных — например, вызов метода, который отсутствует у значения.
URIError
Происходит при некорректном использовании глобальных URI-функций (например, при ошибках в decodeURIComponent).
AggregateError
Инкапсулирует несколько ошибок в одной. Полезен, когда нужно вернуть набор ошибок (Promise.any может выбросить AggregateError, если все промисы отклонены).
InternalError
Указывает на внутреннюю ошибку движка JavaScript (редко встречается в прикладном коде).
RangeError
Возникает, когда значение выходит за допустимый диапазон (например, при создании массивов или при работе с числами).
Блок try…catch…finally — базовый инструмент обработки исключений
Используйте try…catch, чтобы перехватить ошибки во время выполнения. finally выполняется всегда и подходит для очистки ресурсов.
try {
// Код, который ожидаемо может выбросить ошибку
doSomething();
} catch (error) {
// Обработка ошибки: логирование, преобразование, повторная отправка
console.error(error);
} finally {
// Очистка: закрытие соединений, снятие блокировок и т. п.
cleanup();
}Поведение блоков:
- Если в try не произошло исключения — catch пропускается.
- finally выполняется всегда, даже при return в try или catch.
- Код в try должен быть синтаксически корректным — синтаксические ошибки не поймает обычный try.
Пример с ReferenceError:
try {
console.log(text);
} catch (error) {
console.log(error.message); // например, 'text is not defined'
} finally {
console.log('Выполнится в любом случае');
}Как выбрасывать собственные ошибки
Оператор throw позволяет создавать свои исключения. Можно выбрасывать любой тип: строки, объекты, экземпляры Error.
Пример с простым сообщением:
const data = getData();
try {
if (!data) {
throw 'Нет данных';
}
console.log(data);
} catch (error) {
console.log(error); // 'Нет данных'
}Рекомендация: используйте объекты Error для совместимости с системами логирования и стеками.
throw new Error('Нет данных');Тогда в catch можно обращаться к error.name и error.message.
Создание собственных классов ошибок
Кастомные классы ошибок помогают явной классификации проблем (например, ValidationError, AuthError).
class ValidationError extends Error {
constructor(message, options) {
super(message, options);
this.name = 'ValidationError';
}
}
throw new ValidationError('Неверный формат email');Начиная с современных сред, конструктор Error поддерживает вторым аргументом { cause } для вложенных причин:
const dbError = new Error('Сбой БД');
throw new ValidationError('Ошибка валидации', { cause: dbError });Асинхронные ошибки: промисы и async/await
Промисы и async/await требуют специального подхода:
- Для промисов используйте .catch() или обработчик ошибок на уровне цепочки.
- Для async/await — оборачивайте await в try/catch.
// Promise
fetch('/api').then(res => res.json()).catch(err => console.error('Ошибка запроса', err));
// async/await
async function load() {
try {
const res = await fetch('/api');
const json = await res.json();
} catch (err) {
console.error('Ошибка загрузки', err);
}
}Если задача критична, добавьте тайм-ауты и отменяемые операции, чтобы промис не «висел» бесконечно.
AggregateError и параллельные операции
Когда вы запускаете несколько операций параллельно, полезно собрать все ошибки в одну структуру:
Promise.allSettled([p1, p2, p3]).then(results => {
const errors = results.filter(r => r.status === 'rejected').map(r => r.reason);
if (errors.length) {
throw new AggregateError(errors, 'Несколько ошибок при параллельном выполнении');
}
});Практические паттерны и приемы
Ниже — полезные практики, которые упрощают поддержку кода и делают поведение приложения предсказуемым.
- Центральное логирование: собирайте ошибки в одном месте и отправляйте в систему мониторинга (Sentry, Logstash и т. п.).
- Не подавляйте ошибки молча: если вы перехватываете — логируйте и решайте, можете ли восстанавливать состояние.
- Отделяйте пользовательские сообщения от внутренних: пользователю показывайте простое объяснение, в лог — полный стек.
- Применяйте границы ответственности: компонент/модуль обрабатывает свои ошибки; глобальный обработчик ловит непойманные исключения.
- Используйте уровни ошибок: информационные, предупреждения, критические — это упрощает реакцию команды.
Важно: UX-сообщение должно быть понятным и давать следующие шаги (повторить попытку, связаться с поддержкой, сохранить данные).
Роли и чек-листы при работе с ошибками
Разделим обязанности по ролям — что должен сделать каждый участник команды.
Разработчик:
- Перехватить и логировать ошибки в пределах модуля.
- Выбрасывать специализированные ошибки (ValidationError, AuthError).
- Не блокировать UI без причины.
Технический лидер:
- Определить стандарты ошибок и стратегию логирования.
- Настроить SLIs/SLOs для критичных потоков.
- Обеспечить единый формат ошибок для мониторинга.
QA инженер:
- Покрыть сценарии обработки ошибок тестами (мока ошибок API, невалидные данные).
- Проверить восстановление после ошибок и сообщения пользователю.
Критерии приёмки
- Ошибки логируются в централизованную систему с уровнем и контекстом.
- Пользователь получает понятное сообщение и возможный способ восстановления.
- Система не теряет критичные данные при ошибках.
- Тесты покрывают основные сценарии отказа.
Рунибук: как реагировать на критичную ошибку в продакшене
- Зафиксировать ошибку и собрать контекст (логи, трассировку, время, параметры).
- Вставить временную защиту (feature flag, откат версии) при необходимости.
- Диагностировать первопричину и обеспечить патч.
- После исправления — мониторить систему и писать пост-мортем.
Ментальные модели и эвристики
- Fail fast: выбрасывайте ошибки как можно раньше, чтобы не загромождать состояние.
- Принцип наименьего удивления: сообщения об ошибках должны соответствовать ожиданиям разработчика/пользователя.
- Разделение ответственности: каждый модуль отвечает за свои ошибки и предоставляет понятный контракт.
Когда обработка ошибок может не сработать
- Синтаксические ошибки в коде не будут перехвачены обычным try/catch — их нужно исправлять на этапе сборки/линтинга.
- Ошибки в обработчиках событий, которые запускаются вне промисов, могут потребовать глобальных слушателей (window.onerror, process.on(‘uncaughtException’)).
- Тяжёлые ошибки движка (InternalError) часто требуют обновления среды выполнения.
Тестирование обработки ошибок: примеры тест-кейсов
- Искусственно выбросить ValidationError и проверить, что компонент отображает корректное сообщение.
- Смоделировать отказ API и проверить поведение retry-логики и таймаутов.
- Проверить, что finally выполняется при return в try.
Безопасность и приватность
- Не включайте в сообщения пользователям или в публичные логи чувствительные данные (пароли, токены, PII).
- В логах сохраняйте минимально необходимый контекст для отладки, а детали чувствительных полей маскируйте.
Совместимость и миграция
- При добавлении новых типов ошибок сохраняйте обратную совместимость: старые клиенты не должны ломаться при изменении формата.
- Если используете поле cause, проверьте поддержку целевых сред (старые движки могут не поддерживать).
Частая ошибка: подавление ошибок без действия
Подавление исключения без ведома системы мониторинга приводит к «тихим сбоям», которые трудно обнаружить. Всегда логируйте и, при возможности, уведомляйте команду.
Факт-бокс: ключевые моменты
- try…catch…finally — базовый механизм для перехвата исключений.
- throw позволяет создавать собственные ошибки; используйте объект Error для совместимости.
- Кастомные классы ошибок делают логику обработки яснее.
- Асинхронный код требует try/catch вокруг await или обработки .catch для промисов.
Примеры шаблонов кода (cheat sheet)
Обёртка для async-функций:
function safeAsync(fn) {
return async function(...args) {
try {
return await fn(...args);
} catch (err) {
console.error('Unhandled async error', err);
throw err; // или преобразовать в пользовательскую ошибку
}
};
}Повтор с экспоненциальной задержкой (retry):
async function retry(fn, attempts = 3) {
let i = 0;
while (i < attempts) {
try {
return await fn();
} catch (err) {
i++;
if (i >= attempts) throw err;
await new Promise(r => setTimeout(r, 2 ** i * 100));
}
}
}Диаграмма принятия решения (Mermaid)
flowchart TD
A[Произошла ошибка] --> B{Локальная обработка возможна?}
B -- Да --> C[Перехватить и восстановиться]
B -- Нет --> D[Логировать и поднять на уровень выше]
D --> E{Критичная ошибка?}
E -- Да --> F[Триггерить алерт/откат]
E -- Нет --> G[Превратить в предупреждение и продолжить]Глоссарий (1 строка каждое)
- Исключение: событие, изменяющее обычный поток выполнения программы.
- throw: оператор для выбрасывания исключений.
- try/catch: конструкция для перехвата исключений во время выполнения.
- AggregateError: контейнер для нескольких ошибок.
- cause: свойство ошибки, указывающее на первопричину.
Заключение
Ошибки — это нормальная часть разработки. Систематический подход к их обработке повышает устойчивость приложения и упрощает реакцию на инциденты. Используйте try…catch для локальных сценариев, создавайте семантические классы ошибок, логируйте контекст и не забывайте про безопасность данных при логировании.
Важно: начните с простых правил и постепенно формализуйте стратегию обработки ошибок в команде.
Похожие материалы
Несколько аккаунтов Skype: Multi Skype Launcher
Журнал для работы: повысить продуктивность
Персональные звуки уведомлений на Android
Скачивание шоу Hulu для офлайн‑просмотра
Microsoft Start: персонализированная новостная лента