Гид по технологиям

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

6 min read Разработка Обновлено 16 Dec 2025
Обработка ошибок в JavaScript: полное руководство
Обработка ошибок в 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, невалидные данные).
  • Проверить восстановление после ошибок и сообщения пользователю.

Критерии приёмки

  • Ошибки логируются в централизованную систему с уровнем и контекстом.
  • Пользователь получает понятное сообщение и возможный способ восстановления.
  • Система не теряет критичные данные при ошибках.
  • Тесты покрывают основные сценарии отказа.

Рунибук: как реагировать на критичную ошибку в продакшене

  1. Зафиксировать ошибку и собрать контекст (логи, трассировку, время, параметры).
  2. Вставить временную защиту (feature flag, откат версии) при необходимости.
  3. Диагностировать первопричину и обеспечить патч.
  4. После исправления — мониторить систему и писать пост-мортем.

Ментальные модели и эвристики

  • 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 для локальных сценариев, создавайте семантические классы ошибок, логируйте контекст и не забывайте про безопасность данных при логировании.

Важно: начните с простых правил и постепенно формализуйте стратегию обработки ошибок в команде.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Несколько аккаунтов Skype: Multi Skype Launcher
Программное обеспечение

Несколько аккаунтов Skype: Multi Skype Launcher

Журнал для работы: повысить продуктивность
Productivity

Журнал для работы: повысить продуктивность

Персональные звуки уведомлений на Android
Android.

Персональные звуки уведомлений на Android

Скачивание шоу Hulu для офлайн‑просмотра
Стриминг

Скачивание шоу Hulu для офлайн‑просмотра

Microsoft Start: персонализированная новостная лента
Новости

Microsoft Start: персонализированная новостная лента

Как изменить имя в Epic Games быстро
Гайды

Как изменить имя в Epic Games быстро