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

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

7 min read Разработка Обновлено 08 Jan 2026
Обработка ошибок в JavaScript — руководство
Обработка ошибок в JavaScript — руководство

Буквы на маленьких кусочках бумаги образуют слово «Ошибка»

Почему обработка ошибок важна

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

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

Краткое определение: ошибка — это объект или значение, которое сигнализирует о нарушении нормального выполнения программы.

Важно: выводить пользователю человекопонятные сообщения и отправлять подробные логи в безопасное место для анализа.

Структура встроенных ошибок JavaScript

Встроенные ошибки JavaScript представлены объектами с ключевыми свойствами:

  • name — имя ошибки (например, “ReferenceError”);
  • message — текстовое описание причины;
  • cause — опциональное поле для хранения исходной причины при создании цепочек ошибок.

Факт: большинство ошибок имеет эти свойства; дополнительно можно добавлять свои поля (код ошибки, контекст, метаданные).

Типичные ошибки в JavaScript

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

Синтаксическая ошибка

Синтаксическая ошибка возникает при неверном синтаксисе кода. Примеры:

  • пропущено имя переменной;
  • отсутствует закрывающая фигурная скобка “}”;
  • пропущена закрывающая круглая скобка “)”.

Синтаксические ошибки обычно обнаруживаются парсером и останавливают интерпретацию скрипта.

ReferenceError

ReferenceError возникает, когда код пытается обратиться к несуществующей или недоступной переменной.

Пример: обращение к переменной вне области видимости.

TypeError

TypeError возникает, когда операция применена к значению неподходящего типа. Пример: вызов свойства у значения null или undefined.

URIError

URIError появляется при некорректном использовании функций кодирования/декодирования URI (например, decodeURIComponent с неправильной строкой).

AggregateError

AggregateError используется для упаковки нескольких ошибок в одну — полезно при запуске множества асинхронных задач параллельно. Promise.any() может генерировать AggregateError, если все промисы отвергаются.

InternalError

InternalError сигнализирует о проблеме внутри движка JavaScript — это редко и обычно означает внутреннюю ошибку выполнения.

RangeError

RangeError возникает, когда значение аргумента выходит за допустимый диапазон (например, некорректный длина массива или стек переполнен при рекурсии).

Обработка ошибок с помощью try…catch…finally

Конструкция try…catch…finally предоставляет основной механизм перехвата исключений в рантайме. Базовый шаблон:

try {  
    // Действительный JavaScript-код
} catch (error) {  
    // Обработка ошибки
} finally {  
    // Выполняется в любом случае
}

Поведение:

  • код в try выполняется первым;
  • при выбрасывании ошибки выполнение переходит в catch;
  • блок finally выполняется всегда, даже если ошибка не была выброшена или была повторно выброшена внутри catch.

Важно: код в блоке try должен быть синтаксически корректным — иначе парсер выдаст ошибку до выполнения.

Пример из практики:

try {  
    console.log(text)
} catch (error) {  
    console.log(error.message)
} finally {  
    console.log("Выполнится в любом случае")
}

Если переменная text не определена, catch выведет сообщение об ошибке, а finally — дополнительную строку.

Выбрасывание собственных ошибок (throw)

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

Пример с простым проверочным условием:

const data = getData()

try {
    if (!data) {
        throw "Нет данных"
    }

    console.log(data)
} catch(error) {
    console.log(error) // "Нет данных"
}

Рекомендуется использовать объекты Error:

throw {
    name: "Error name",
    message: "Error message"
}

Или встроенный конструктор Error:

throw new Error("Нет данных")

После этого доступны свойства error.name и error.message:

console.log(error.name) // Error
console.log(error.message) // Нет данных

Расширение Error — кастомные классы ошибок

Иногда полезно добавить собственный тип ошибки для конкретной доменной логики (валидация, ошибка сетевого слоя, ошибка бизнес-правил). Это делает обработку ошибок более предсказуемой и позволяет переключаться по типу ошибки.

Пример кастомного класса ValidationError:

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

Выброс и результат:

throw new ValidationError("Неверный формат поля email")

Получаем объект с name = “ValidationError” и message = “Неверный формат поля email”.

Работа с асинхронным кодом: промисы и async/await

Асинхронные операции требуют отдельного подхода к обработке ошибок.

  • Для промисов используйте .catch():
fetch(url)
  .then(response => response.json())
  .catch(err => console.error("Ошибка загрузки", err))
  • При использовании async/await оборачивайте await в try/catch:
async function load() {
  try {
    const res = await fetch(url)
    const data = await res.json()
    return data
  } catch (err) {
    console.error("Ошибка в load:", err)
    throw err
  }
}

Альтернатива — вернуть объект-результат с ошибкой вместо выбрасывания, что упрощает контроль потока без исключений.

async function safeFetch() {
  try {
    const res = await fetch(url)
    return { ok: true, data: await res.json() }
  } catch (err) {
    return { ok: false, error: err }
  }
}

Выбор подхода зависит от контрактов вашего кода и архитектуры.

Паттерны и лучшие практики

  • Локализуйте обработку ошибок там, где можно восстановить состояние.
  • Логируйте детальную информацию (стек, контекст) в защищённый лог-сервис, а пользователю показывайте общее сообщение.
  • Используйте кастомные классы ошибок для различных слоёв приложения (ValidationError, NetworkError, DatabaseError).
  • Не используйте “пустой” catch без логирования — это скрывает проблемы.
  • Валидируйте входные данные до выполнения критичных операций.
  • Не доверяйте входам извне — всегда проверяйте типы и диапазоны перед выполнением.

Плейбук: минимальная методика обработки ошибок

  1. Проверка входных данных и валидация.
  2. Локальный try/catch вокруг кода, который может восстановиться.
  3. Если восстановиться нельзя, собрать контекст и пробросить (rethrow) ошибку вверх.
  4. В верхнем уровне приложения показать дружественное сообщение пользователю и отправить подробный журнал в систему мониторинга.
  5. При повторяющихся ошибках — ввести лимит повторных попыток и circuit breaker.

Мини-правило: логируй локально минимально, централизуй полноту логов для диагностики.

Когда обработка ошибок терпит неудачу

Контрпример: перехват ошибок в одном месте без понимания причин может привести к маскировке источника проблемы. Например, если catch возвращает значение по умолчанию без логирования, баг может замолчать и проявиться позже в другом месте.

Другой сценарий: обработка ошибок на клиенте как единственный механизм — при проблемах на сервере это не даст достаточной информации для восстановления.

Чек-листы по ролям

Разработчик фронтенда:

  • проверяет входные данные;
  • использует user-friendly сообщения;
  • отправляет логи с минимальным контекстом пользователя (без персональных данных).

Бэкенд-разработчик:

  • выделяет коды ошибок и уникальные идентификаторы инцидентов;
  • хранит стек и метаданные в защищённом логе;
  • различает ошибки инфраструктуры и бизнес-ошибки.

SRE/операции:

  • настраивает алерты на частые или критичные ошибки;
  • анализирует тренды ошибок и выполняет постмортемы.

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

  • Для каждого критичного пути выполнена проверка входных данных;
  • Любая ошибка либо корректно восстанавливается, либо логируется и поднимается как инцидент;
  • Юзер получает понятное сообщение вместо «Uncaught Error»;
  • ЛОГИ не содержат персональных данных в явном виде.

Тестовые случаи и приёмка

Примеры тестов для обработки ошибок:

  • Мокнуть отсутствие данных и проверить, что показывается сообщение “Нет данных”.
  • Симулировать отказ сети и проверить логи и поведение UI (retry/backoff).
  • Передать неверный формат и проверить, что ValidationError имеет правильное имя и сообщение.
  • Проверить, что finally-блок всегда запускается.

Безопасность и конфиденциальность

  • Никогда не отправляйте в общедоступные логи персональные данные (PII).
  • Логи должны храниться в защищённой системе с контролем доступа.
  • В пользовательских сообщениях избегайте утечки внутренней структуры приложения или путей до файлов.

Примечание для GDPR-проектов: в логах храните только минимально необходимую информацию и используйте псевдонимизацию, если лог содержит идентификаторы пользователей.

Практичные сниппеты и шпаргалка

  • Простая обёртка для безопасного выполнения функции:
function safeRun(fn, fallback) {
  try {
    return fn()
  } catch (err) {
    console.error(err)
    return fallback
  }
}
  • Универсальный обработчик промисов:
Promise.resolve(promise)
  .then(result => [null, result])
  .catch(err => [err])
  • Полезный шаблон для асинхронных ошибок:
async function withRetries(fn, attempts = 3) {
  let lastErr
  for (let i = 0; i < attempts; i++) {
    try {
      return await fn()
    } catch (err) {
      lastErr = err
      // exponential backoff или пауза
    }
  }
  throw lastErr
}

Модель принятия решений (Mermaid)

flowchart TD A[Произошла ошибка] –> B{Можно восстановиться локально?} B – Да –> C[Попытка восстановления] B – Нет –> D[Собрать контекст и пробросить] C –> E[Успешно восстановлено] C –> D D –> F[Показать дружественное сообщение] D –> G[Отправить лог в мониторинг]

Краткая справка: термины в одну строку

  • Исключение: событие, прерывающее обычный поток выполнения;
  • Stack trace: стек вызовов в момент ошибки;
  • Rethrow: повторное выбрасывание ошибки после обработки;
  • Circuit breaker: механизм отключения попыток при серии неудач.

Частые ошибки и как их избегать

  • “Пустой” catch без логирования — всегда логируйте хотя бы минимально.
  • Выбрасывание строк вместо объектов — используйте Error или наследников.
  • Логирование PII — удаляйте/маскируйте личные данные перед отправкой.

Заключение

Обработка ошибок — не опция, а необходимая часть архитектуры приложения. Простая и последовательная стратегия позволяет сохранить качество UX, упростить отладку и уменьшить время восстановления. Кастомные ошибки, централизация логов и аккуратный подход к асинхронному коду — основа надёжной системы обработки ошибок.

FAQ

Как выбрать между возвратом результата с ошибкой и выбрасыванием исключения?

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

Нужно ли наследовать от Error для всех пользовательских ошибок?

Рекомендуется наследовать от Error, чтобы сохранять совместимость с инструментами, ожидающими объект ошибки, и иметь корректный stack trace.

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

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

Bluetooth не отображается — как вернуть в Диспетчер устройств
Windows

Bluetooth не отображается — как вернуть в Диспетчер устройств

Select and Mask в Photoshop CC 2018 — извлечение и сглаживание
Photoshop

Select and Mask в Photoshop CC 2018 — извлечение и сглаживание

Замена mSATA SSD в планшете Windows 8
Апгрейд

Замена mSATA SSD в планшете Windows 8

Как стримить Xbox Game Pass на мобильный без контроллера
Гейминг

Как стримить Xbox Game Pass на мобильный без контроллера

Изменить цвет папок на Mac
macOS

Изменить цвет папок на Mac

Признаки взлома камеры и как её защитить
Кибербезопасность

Признаки взлома камеры и как её защитить