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 в промисах или специальные функции очистки в обработчике.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ