Từ Node 15, mặc định của --unhandled-rejections đổi từ warn sang throw: nếu không có handler unhandledRejection, rejection không bắt được sẽ nổi lên thành uncaught exception và làm chết tiến trình. Trước đó nó chỉ in cảnh báo và app chạy tiếp — đây là lý do code cũ nâng version Node xong bỗng bị crash.
Cách xử lý được khuyến nghị: để tiến trình chết, nhưng chết có kiểm soát.
process.on('unhandledRejection', (reason) => {
logger.fatal({ err: reason }, 'unhandled rejection')
throw reason // turn it into uncaughtException
})
process.on('uncaughtException', (err) => {
logger.fatal({ err }, 'uncaught exception')
gracefulShutdown() // stop taking traffic, then exit
setTimeout(() => process.exit(1), 10000).unref()
})Lý do không nuốt lỗi rồi chạy tiếp: khi tới uncaughtException, một handler đã bị bỏ dở giữa chừng — connection, transaction, biến trạng thái có thể đang ở trạng thái không nhất quán. Tiếp tục phục vụ request trên trạng thái đó là nguồn của bug khó tái hiện. Cho tiến trình khởi động lại (K8s, PM2, systemd) là đường an toàn.
Phòng ngừa ở tầng code: luôn await hoặc .catch() mọi promise; bọc handler async của Express bằng wrapper bắt lỗi (Express 4 không tự bắt lỗi async); bật rule lint no-floating-promises của typescript-eslint.