JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Глобальная обработка ошибок

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

Модуль 4: Node.js, Next.js и Angular
2 уровень , 9 лекция
Открыта

1. Почему вообще нужна глобальная обработка ошибок?

В идеальном мире все ошибки обрабатываются локально: вы заботливо оборачиваете свой код в try/catch, пишете обработчики для промисов, не забываете про колбэки. Но реальный мир далёк от идеала. Иногда ошибки вылетают из таких глубин, что их просто негде поймать. Программа неожиданно завершает работу — и ваш сервер, например, перестаёт отвечать на запросы. Не очень приятно, правда?

Вот типичные сценарии, когда ошибка может быть не поймана:

  • Вы забыли обработать ошибку в колбэке.
  • В промисе забыли добавить .catch().
  • В синхронном коде ошибка возникла вне блока try/catch.
  • В сторонней библиотеке произошла ошибка, которую она не обработала.

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

Иногда хочется:

  • Записать ошибку в лог (чтобы потом разбираться, почему всё сломалось).
  • Попробовать корректно завершить работу (например, закрыть файлы или соединения).
  • Уведомить администратора или отправить оповещение.

Для этого и существует глобальный обработчик ошибок.

2. Как работает process.on('uncaughtException', ...)?

Node.js предоставляет специальное событие процесса — 'uncaughtException'. Оно срабатывает, когда в вашем приложении возникает исключение, которое никто не поймал.

Синтаксис:

process.on('uncaughtException', (err) => {
  // Ваш код обработки ошибки
});

Параметры:

  • err — объект ошибки (Error), который никто не обработал.

Пример:

process.on('uncaughtException', (err) => {
  console.error('Глобально поймана ошибка:', err.message);
  // Можно записать ошибку в лог-файл или уведомить администратора
  // Завершаем процесс через некоторое время
  setTimeout(() => {
    process.exit(1);
  }, 1000);
});

// Вот тут ошибка не обрабатывается локально!
setTimeout(() => {
  throw new Error('Что-то пошло не так!');
}, 500);

Что произойдёт:

  • Через 500 мс выбрасывается ошибка.
  • Нет ни одного локального обработчика — ни try/catch, ни .catch().
  • Срабатывает обработчик uncaughtException.
  • Ошибка выводится в консоль, и через секунду процесс завершает работу.

3. Важные нюансы и подводные камни

Обработка ошибок ≠ «исправление» ошибок

Глобальный обработчик ошибок — это не волшебная палочка, которая «починит» вашу программу. Он нужен для того, чтобы:

  • Записать ошибку в лог,
  • Корректно завершить работу,
  • Сделать «последние слова» вашего приложения.

ВАЖНО: После возникновения непойманной ошибки состояние приложения может оказаться непредсказуемым. Например, если ошибка произошла где-то в середине обработки данных, ваши переменные могут быть в «полусыром» состоянии. Поэтому рекомендуется завершать процесс после обработки uncaughtException.

Не пытайтесь «продолжать работу» после непойманной ошибки

Node.js официально советует: после возникновения uncaughtException завершайте процесс. Не стоит пытаться «вырулить» и продолжить работу — это может привести к ещё большим проблемам (например, повреждению данных).

process.on('uncaughtException', (err) => {
  console.error('Ошибка! Сервер будет остановлен.');
  setTimeout(() => {
    process.exit(1);
  }, 1000); // Даём время дописать логи и завершить соединения
});

Можно ли ловить ошибки в асинхронном коде?

  • Если ошибка возникает в асинхронном колбэке (например, внутри setTimeout), и её никто не поймал — она попадёт в uncaughtException.
  • Если ошибка возникает в промисе, и у промиса нет .catch() — она попадёт в другое событие: unhandledRejection (о нём чуть позже).

Пример с промисом:

process.on('unhandledRejection', (reason, promise) => {
  console.error('Обнаружен необработанный отказ промиса:', reason);
  process.exit(1);
});

Promise.reject(new Error('Ошибка в промисе!'));

4. Применение в реальном приложении

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

main.js:

// Глобальный обработчик ошибок
process.on('uncaughtException', (err) => {
  console.error('Глобальная ошибка:', err);
  // Здесь можно записать ошибку в файл:
  // fs.appendFileSync('errors.log', err.stack + '\n');
  setTimeout(() => {
    console.log('Сервер завершает работу из-за критической ошибки.');
    process.exit(1);
  }, 1000);
});

// Пример: ошибка в асинхронном коде
setTimeout(() => {
  throw new Error('Ошибка в асинхронном коде!');
}, 500);

// Пример: ошибка в синхронном коде (будет поймана тут же)
throw new Error('Ошибка в синхронном коде!');

Что происходит:

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

5. Полезные нюансы

Советы по использованию и best practices

  • Используйте глобальный обработчик только как «страховку» — не заменяйте им локальную обработку ошибок!
  • Завершайте процесс после глобальной ошибки — не пытайтесь продолжать работу.
  • Логируйте ошибки — это поможет найти и исправить баги.
  • Уведомляйте администратора — можно отправить письмо, сообщение в Telegram, Discord и т.д.
  • Не используйте глобальный обработчик для «тихого» игнорирования ошибок — это приведёт к накоплению проблем.

Альтернативы и расширения: unhandledRejection

Для промисов, у которых нет обработчика ошибок (.catch()), существует отдельное событие — unhandledRejection.

process.on('unhandledRejection', (reason, promise) => {
  console.error('Необработанный отказ промиса:', reason);
  // Можно логировать и завершать работу как с uncaughtException
  process.exit(1);
});

Promise.reject(new Error('Ошибка в промисе!'));

Рекомендация:
Обрабатывайте оба события (uncaughtException и unhandledRejection), чтобы быть уверенными, что не пропустите ни одну ошибку.

6. Типичные ошибки при работе с глобальной обработкой ошибок

Ошибка №1: попытка продолжать работу после непойманной ошибки.
После глобальной ошибки состояние памяти может быть непредсказуемым. Даже если кажется, что «всё работает», данные могут быть повреждены или соединения — некорректны. Всегда завершайте процесс через process.exit().

Ошибка №2: использование глобального обработчика вместо локального.
Глобальный обработчик — это последняя линия обороны, а не основной способ обработки ошибок. Всегда старайтесь ловить ошибки локально, чтобы можно было корректно восстановить работу или показать пользователю понятное сообщение.

Ошибка №3: отсутствие логирования ошибок.
Если вы просто завершаете процесс без логирования, то потом будет очень трудно понять, что же пошло не так. Всегда записывайте ошибки хотя бы в консоль, а лучше — в отдельный лог-файл.

Ошибка №4: не обрабатываются необработанные промисы.
Многие забывают про событие unhandledRejection, и ошибки в промисах остаются незамеченными. Добавьте обработчик и для этого события.

Ошибка №5: забыли удалить временные файлы или закрыть соединения.
Перед завершением процесса по ошибке стоит попытаться корректно освободить ресурсы. Для этого можно использовать finally в промисах или специальные функции очистки в обработчике.

1
Задача
Модуль 4: Node.js, Next.js и Angular, 2 уровень, 9 лекция
Недоступна
Вывод сообщения о непойманной ошибке
Вывод сообщения о непойманной ошибке
1
Задача
Модуль 4: Node.js, Next.js и Angular, 2 уровень, 9 лекция
Недоступна
Глобальная обработка ошибки в асинхронном коде
Глобальная обработка ошибки в асинхронном коде
3
Опрос
Выбрасывание пользовательских ошибок, 2 уровень, 9 лекция
Недоступен
Выбрасывание пользовательских ошибок
Выбрасывание пользовательских ошибок
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ