Обработка ошибок
Тема дорожной карты · Node.js
Надёжная обработка ошибок критически важна для продакшн-приложений Node.js, поскольку необработанные ошибки могут привести к аварийному завершению процесса или оставить его в неопределённом состоянии, при котором он будет молча возвращать некорректные ответы. Node.js предоставляет несколько механизмов для перехвата ошибок: блоки try/catch для синхронного кода и функций с async/await, обработчики .catch() для Promise, колбэки с ошибкой в первом аргументе для устаревших асинхронных API, обработчик process.on('uncaughtException') для синхронных исключений, которые обошли все прочие обработчики, и process.on('unhandledRejection') для Promise, которые так и не были обработаны. В Express.js middleware с четырьмя аргументами (err, req, res, next) является стандартным паттерном централизованных HTTP-ответов на ошибки, превращающим внутренние ошибки в соответствующие статус-коды и JSON-ответы без утечки трассировок стека клиентам. Операционные ошибки (например, недоступность базы данных, ошибка валидации) следует отличать от программных ошибок (например, TypeError, ReferenceError) — программные ошибки, как правило, требуют корректного перезапуска процесса под управлением супервизора вроде pm2 или оркестратора контейнеров. Библиотеки структурированного логирования, такие как pino, должны фиксировать детали ошибок, включая трассировки стека и контекст запроса, чтобы ошибки серверного приложения Node.js были полностью видны в продакшн-системах мониторинга.
Как это работает
Обработка ошибок — сердце Node: event loop libuv проходит фазы timers, pending I/O callbacks, idle/prepare, poll, check, close-callbacks. async/await — современный примитив; promises — механизм под капотом; callbacks — legacy. setImmediate запускается на следующей итерации; process.nextTick — до любого I/O, злоупотребление ломает loop. Worker threads (worker_threads) гонят JS параллельно; у них отдельные event loop, изолированная память, обмен сообщениями.
Когда применять
async/await — везде в новом коде; callbacks — для legacy или streams. Promise.all/allSettled — для параллельного I/O когда независимы. CPU-bound работа — в worker_threads или отдельный сервис; никогда не блокируйте main loop. AbortController — для отмены долгих операций. Профилируйте event-loop lag (perf_hooks.monitorEventLoopDelay) в production — устойчивый lag > 50ms = проблема.
Типичные ошибки
Ловушки Обработка ошибок: забытый await → unhandled promise rejection (Node логирует warning, скоро будет crash); Promise.all падает на первом, когда хотели partial success (Promise.allSettled); глубокие then-цепочки там, где async/await читался бы линейно; setTimeout(0) для "yield в loop" (используйте setImmediate); рекурсия, растящая stack вместо возврата promise.