Node.js 14 trở về trước: unhandled rejection chỉ print warning, process tiếp tục — silent failure nguy hiểm.
Node.js 15+: mặc định crash process với exit code 1 — breaking change.
Hierarchy xử lý đúng:
- try/catch trong async functions là chính,
.catch()chain cho fire-and-forget promises,process.on('unhandledRejection', (reason) => { logger.error('Unhandled rejection', reason); gracefulShutdown(1); })là safety net cuối — không phải cơ chế chính.process.on('uncaughtException', (err) => { logger.error(err); gracefulShutdown(1); })cho sync throws
Monitoring: gửi đến Sentry trước khi shutdown — Sentry.captureException(reason); await Sentry.flush(2000).
Graceful shutdown: stop accepting requests, wait for in-flight, close DB, exit.
Dùng ESLint rule @typescript-eslint/no-floating-promises để catch missing awaits lúc compile time — tốt hơn runtime detection.
Node.js 14 and earlier: unhandled rejections only print a warning and the process continues — silent failures are dangerous.
Node.js 15+: crashes the process with exit code 1 by default — a breaking change.
Proper handling hierarchy:
- try/catch inside async functions is the primary mechanism,
.catch()chaining for fire-and-forget promises,process.on('unhandledRejection', (reason) => { logger.error('Unhandled rejection', reason); gracefulShutdown(1); })as a final safety net — not the primary mechanism.process.on('uncaughtException', (err) => { logger.error(err); gracefulShutdown(1); })for synchronous throws
Monitoring: send to Sentry before shutting down — Sentry.captureException(reason); await Sentry.flush(2000).
Graceful shutdown: stop accepting requests, wait for in-flight requests, close DB connections, exit.
Use the ESLint rule @typescript-eslint/no-floating-promises to catch missing awaits at compile time — better than runtime detection.