What are uncaughtException and unhandledRejection, and how should you handle them?

3 minadvancednodejsuncaughtexceptionunhandledrejectionprocesscrash

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.