Catching Unhandled Promise Rejections and uncaughtException in Node.js
Tags: node, express, unhandledrejection, uncaughtexception
Published: 2023-04-02 11:24:38

Node.js is an event-driven platform that executes code asynchronously. This means that errors thrown in a callback or a promise chain that are not caught will not be handled by the uncaughtException event-handler and will disappear without warning. Although recent versions of Node.js added a warning message when an unhandled rejection occurs, this does not constitute proper error handling. 

This can be a significant issue for developers, who may overlook adding catch clauses to promise chains. Fortunately, Node.js provides a mechanism for catching these unhandled rejections, the unhandledRejection event. In this blog post, we will discuss how to catch unhandled promise rejections in Node.js and why it is essential.

## Why Catching Unhandled Promise Rejections and uncaughtException is Important
Uncaught errors in Node.js can cause serious issues, such as memory leaks, unexpected termination, and server downtime. Therefore, it is essential to catch these errors in a controlled manner to minimize their impact on the system. By subscribing to the `process.on('unhandledRejection', callback)` and `process.on('uncaughtException', callback)` events, you can catch these errors and handle them appropriately.

Here's an example code snippet that could result in an unhandled promise rejection:

```
import { User } from './models/user.model';

async function getUserById(id: string) {
  return await User.findById(id);
}

async function updateUserName(id: string, name: string) {
  const user = await getUserById(id);
  user.name = name;
  return user.save();
}

// Call updateUserName function with invalid ID
updateUserName('invalidId', 'John Doe');
```
In this code snippet, the `getUserById` function returns a promise that resolves with the user object with the given ID. The updateUserName function uses this function to find the user with the given ID, updates the user's name, and saves the changes to the database.

However, if the updateUserName function is called with an invalid ID, the getUserById function will throw an error, resulting in an unhandled promise rejection.

## Handling Unhandled Promise Rejections
The simplest way to handle unhandled promise rejections is to add a .catch clause within each promise chain call and redirect it to a centralized error handler. However, relying solely on developer discipline is a fragile way of building an error handling strategy. Hence, using a graceful fallback to subscribe to process.on('unhandledRejection', callback) is a recommended approach.

Here is an example of how to catch unhandled promise rejections:

```
process.on('unhandledRejection', (reason: string, p: Promise<any>) => {
  console.error('Unhandled Rejection at:', p, 'reason:', reason);
});
```
This code subscribes to the unhandledRejection event and prints the unhandled rejection's reason and promise to the console. By doing this, you can identify where the rejection occurred and handle it appropriately.

## Handling Uncaught Exceptions
Uncaught exceptions occur when an error is thrown but not caught by any try-catch block or error handler. These exceptions can lead to the application's unexpected termination or cause memory leaks that can lead to server downtime. To handle these uncaught exceptions, you can subscribe to the process.on('uncaughtException', callback) event.

Here is an example of how to catch uncaught exceptions:

```
process.on('uncaughtException', (error: Error) => {
  console.error(`Caught exception: ${error}\n` + `Exception origin: ${error.stack}`);
});
```
This code subscribes to the uncaughtException event and prints the exception's stack trace to the console. By doing this, you can identify where the exception occurred and handle it appropriately.

Catching unhandled promise rejections and uncaught exceptions is crucial to avoid unexpected behavior in Node.js applications. By subscribing to the `process.on('unhandledRejection', callback)` and `process.on('uncaughtException', callback)` events and using graceful fallbacks, you can catch and handle these errors in a controlled manner. The examples provided in this article demonstrate how to catch these errors and handle them appropriately, thereby ensuring the stability and reliability of your Node.js applications.