Blog |

Handling Node.js Exceptions

Handling Node.js Exceptions
Table of Contents

Debugging errors is the hardest part of programming. Errors can appear in your code in a variety of ways, whether as syntax errors, errors in logic, or the most dreaded of all, runtime errors. Runtime errors occur whenever something unexpected occurs in your application, and they often lead to catastrophic issues that can crash your program.

Like many languages, Node.js provides a mechanism to anticipate errors before they occur. When an error occurs in your code, it turns into an object called an exception. Properly handling these exceptions allows you to recover gracefully from unforeseen issues, resulting in a much better user experience.

In this post, we'll take a look at what causes these errors in Node.js, and how to recover from them.

What can cause an error exception in Node.js?

Runtime errors can occur in a variety of ways. One example includes referencing an undefined variable, or passing the wrong type to an argument.

Other common Node.js errors include:

  1. EvalError: errors that occur within the global function eval
  2. RangeError: these errors occur when you attempt to access a variable outside its range, such as trying to get the fifth element of an array with only three items
  3. ReferenceError: these errors arise when you attempt to use a variable that doesn't exist
  4. SyntaxError: these errors come from invalid code
  5. TypeError: this occurs when you attempt to use a variable that is not a valid type
  6. URIError: this error occurs whenever encodeURI or decodeURI are given invalid parameters

In most cases, these exceptions occur outside of your control. For example, if you run the following line of code:

null.toString()

You should expect to receive the following error:

Uncaught TypeError: Cannot read property 'someMethod' of null

Node.js Throw Exception

However, you can also throw an error yourself:

throw new Error('Throw makes it go boom!')

Why would you want to do this? Well, consider the case where you know an error can occur. For example, suppose you have a calculator app written in Node.js, and you want to throw an error if the denominator in the division function is a zero:

function divide(numerator, denominator) {
  if (denominator == 0) {
    throw new Error('divide by zero!')
  } 
  else {
    return numerator / denominator;
  }
}

Why would you want to do this? Well, admittedly, catching a generic Error is not very helpful. But you can create a custom error subclass:

class DivideByZeroError extends Error {
  constructor(message) {
    super(message);
    this.name = "DivideByZeroError";
  }
  }

This allows you to set up a more appropriate error in the code:

function divide(numerator, denominator) {
  if (denominator == 0) {
    throw new DivideByZeroError('divide by zero!')
  } 
  else {
    return numerator / denominator;
  }
}

If you take this concept further, you could throw errors for any kind of issue. Compare the following ways of handling errors:

let user = JSON.parse(json);

if (!user.age || user.age < 0) {
  throw new InvalidAgeError("No field: age");
}
if (!user.name) {
  throw new MissingNameError("No field: name");
}
if (!user.address) {
  throw new Error("Required");
}

By throwing your own exception, your immediate benefit will be a more legible code base, as in the case of age and name above. But your error classes will also show up in your logs and data analytics pipelines, which can help you narrow down specifically what problems may be occurring. When trying to track down an error, it can be hard to rationalize what Error is supposed to mean, as opposed to InvalidAge or MissingName.

Regardless, catching errors like this is essential in Node.js. Because Node runs in an asynchronous environment, an event that occurs sometimes doesn't have an immediate effect. In our example above, we tried to rely on user.address in a situation where it doesn't exist, however, the program will crash, but not when we might expect it to! On the other hand, try/catch operates synchronously; as soon as an error is thrown, the program halts, allowing you to recover without unintended side effects.

How do you protect your application against exceptions?

If you know where an error can occur, your next likely step would be to prevent this error from taking down your application or upsetting your user. To prevent this, Node.js has a special syntax called the try-catch block.

As its name suggests, a try-catch block lets you run a piece of code, and if an error is thrown, halts the program and lets you recover. Let's take a look at how that might work in practice with our earlier toString() example. Suppose you have a function like this:

function showString(var) {
  return var.toString();
}

Now, we can't guarantee that toString() will work on var. Maybe it's null, or maybe this function is called on some dynamic user input we can't control. Since we know we have the potential to run into a TypeError here, we can use a try-catch block to anticipate this error and do something else:

function showString(var) {
  try {
    return var.toString()
  } catch (TypeError) {
    // report error message to the user
    return "Error! String not given";
  }
}

If there is no error, then the code in the catch block is never executed. Keep in mind that the error provided to the catch statement must match the error you expect, or else that code won't run. Unfortunately, this can prevent your code from catching errors, and try-catch not working.

If you think an error might occur, but you aren't sure what its type would be, and you want to ensure that you catch any potential errors, you can just protect against the base Error class:

try {
  someFunction();
}
catch (Error) {
  // do something else
}

Since all errors extend this base Error class, it will catch any thrown errors.

Another neat addition is the ability to add a finally statement to these lines. For example:

try {
  openDatabase();
  updateDatabase(x, y);
}
catch (Error) {
  return "Error! Database could not be updated.";
}
finally {
  // always a good idea to close a database connection when you're done
  closeDatabase();
}

finally will always execute at the end of the try-catch block, regardless of whether or not the catch statement catches an error. It can clean up any remaining state, such as closing an open database connection. At least one catch or a finally must be present in a try block.

Wrapping Up

Exceptions and try-catch blocks aren't unique to Node.

We've talked a lot about exceptions in this article. It's important to keep in mind that errors can still occur in alarming volume, despite your best efforts to prevent them. Mistaken code can always make it to production, and rather than foist availability issues onto your users, a platform like Rollbar can help you fix unforeseen issues before they become problems. With its real-time Node error monitoring, AI-assisted workflows, and root cause analysis, Rollbar can protect your app by predicting problems before they affect your users.

Track, Analyze and Manage Errors With Rollbar

Managing errors and exceptions in your code is challenging. It can make deploying production code an unnerving experience. Being able to track, analyze, and manage errors in real-time can help you to proceed with more confidence. Rollbar automates error monitoring and triaging, making fixing errors easier than ever. Try it today.

"Rollbar allows us to go from alerting to impact analysis and resolution in a matter of minutes. Without it we would be flying blind."

Error Monitoring

Start continuously improving your code today.

Get Started Shape