The Silent Killers in Node.js: uncaughtException and unhandledRejection 🐦
Ali nazari

Ali nazari @silentwatcher_95

About: Just a tech

Location:
Earth 🌍
Joined:
Jul 30, 2022

The Silent Killers in Node.js: uncaughtException and unhandledRejection 🐦

Publish Date: Jun 27
5 0

Node.js is beloved for its non‑blocking, event‑driven architecture—but that same model can let critical errors slip through the cracks. Two of the most common culprits are:

  • Uncaught Exceptions: errors thrown but never caught
  • Unhandled Promise Rejections: promise rejections without a .catch()

Left unchecked, either will terminate your process without giving you a chance to clean up or alert your team. In this post, we’ll explore what these events are, why they matter, and how to handle them properly in production.


Understanding the Error Events

process.on('uncaughtException')

process.on('uncaughtException', (err) => {
  console.error('💥 Uncaught Exception:', err);
  // Last‑ditch cleanup or logging…
  shutdownAndExit();
});
Enter fullscreen mode Exit fullscreen mode
  • When it fires: A synchronous exception bubbles all the way up without a surrounding try/catch.
  • Default behavior: Node logs the error and crashes the process.

process.on('unhandledRejection')

process.on('unhandledRejection', (reason, promise) => {
  console.error('🚨 Unhandled Rejection:', reason);
  // You might even re‑throw to convert it into an uncaughtException
  throw reason;
});
Enter fullscreen mode Exit fullscreen mode
  • When it fires: A Promise rejects and there’s no .catch().
  • Since Node.js v15+: These are treated like uncaught exceptions and will crash the process by default.

Why You Can’t Ignore Them

  1. Data Integrity: Without cleanup, open database connections or in‑flight writes can be lost.
  2. Resource Leaks: Timers, sockets, and file handles may never close.
  3. Silent Failures: Your monitoring may not detect the root cause if the process simply disappears.
  4. Security Risks: An attacker‑triggered exception could leave your app in an inconsistent or vulnerable state.

Best Practices for Handling Fatal Errors

✅ Do ❌ Don’t
Log full stack traces and context (request IDs, payloads, etc.) Continue running as if nothing happened
Report to external error‑tracking services (Sentry, Datadog, etc.) Swallow errors or leave handlers empty
Gracefully shut down: close DB, stop accepting new requests Call process.exit(0) (signals success)
Use a supervisor (e.g., PM2, Docker restart policy) Believe you can recover 100% safely in‑process
Convert unhandled rejections into exceptions (throw reason) Ignore Node.js v15+ default behavior—explicit is better

Putting It All Together: Graceful Shutdown

// app.js
const http = require('http');
const server = http.createServer((req, res) => {
  // your handler logic…
  res.end('Hello World');
});

server.listen(3000, () => console.log('Server listening on 3000'));

function shutdownAndExit() {
  console.log('🔒 Closing server…');
  server.close(() => {
    console.log('✅ Server closed. Exiting.');
    process.exit(1);
  });
  // Force‑exit after 5s
  setTimeout(() => process.exit(1), 5000);
}

process.on('uncaughtException', (err) => {
  console.error('💥 Uncaught Exception:', err);
  shutdownAndExit();
});

process.on('unhandledRejection', (reason) => {
  console.error('🚨 Unhandled Rejection:', reason);
  // Optionally convert into uncaughtException for unified handling
  throw reason;
});
Enter fullscreen mode Exit fullscreen mode
  1. Log the error immediately for diagnostics.
  2. Close the HTTP server so no new connections are accepted.
  3. Exit with a non‑zero code to signal failure to your orchestrator.

Beyond the Basics

  • Domain-based separation: (deprecated) can isolate error scopes, but has pitfalls.
  • Worker threads: use message passing and parent thread supervision.
  • Crash-only design: embrace frequent restarts; keep startup fast and idempotent.

Conclusion

Unhandled exceptions and rejections are among the stealthiest threats to your Node.js application’s stability.

By proactively catching them, logging with context, and performing a graceful shutdown, you turn silent killers into manageable events—keeping your services resilient and your team informed.


If you found this helpful, feel free to share

Let’s connect!!: 🤝

LinkedIn
GitHub

Comments 0 total

    Add comment