What are uncaughtException and unhandledRejection, and how should you handle them?
Quick Answer
`uncaughtException` fires when a synchronous error bubbles up with no handler; `unhandledRejection` fires when a rejected promise has no .catch. Treat both as signs the process is in an unknown state: log the error, do minimal cleanup, and exit so a process manager restarts a fresh instance — don't resume normal operation.
Detailed Answer
Answer: These are last-resort process-level events, not a substitute for local error handling.
uncaughtException — a thrown error propagated to the top of the stack with no try/catch:
process.on('uncaughtException', (err) => {
logger.fatal(err);
// do minimal synchronous cleanup, then exit
process.exit(1);
});
unhandledRejection — a promise rejected with no .catch/try-catch:
process.on('unhandledRejection', (reason) => {
logger.error('Unhandled rejection', reason);
process.exit(1);
});
(In modern Node, an unhandled rejection terminates the process by default.)
Why you should exit, not continue:
- After an uncaught exception the application is in an undefined state — resources may be half-updated, locks held, connections dangling. Continuing risks corrupted data and memory leaks.
- Best practice (per Node docs): log, release critical resources, and let the process crash, then rely on a process manager (PM2, systemd, Kubernetes) to restart a clean instance.
What NOT to do:
// ❌ Anti-pattern: swallow and keep running
process.on('uncaughtException', () => { /* ignore */ });
This turns crashes into silent corruption.
The real fix: handle errors where they occur (try/catch, .catch, error middleware). These global handlers are a safety net for logging and orderly shutdown — not normal control flow.