Every modern programming language has the ability to handle and throw exceptions, and JavaScript is no exception. In this column, I discuss why, how, and when you should make use of JavaScript’s try catch statement as well as throwing your own custom errors. A try catch in any programming language is a block in which code can execute in a way where an exception can safely occur in a way that won’t result in the application’s abnormal termination.

With a try catch, you can handle an exception that may include logging, retrying failing code, or gracefully terminating the application. Without a try catch, you run the risk of encountering unhandled exceptions. Try catch statements aren’t free in that they come with performance overhead. Like any language feature, try catches can be overused.

Note: This column and the other columns in this series are meant to add color to the docs and concepts therein, rather than as a less comprehensive duplication of that content.

Context

How many times have you used an application and something goes wrong, interrupting your work without warning and without any context for what went wrong? You call support and over the next few hours or perhaps days, there’s an attempt by many to work on the problem. If you’re lucky, the problem gets resolved quickly so that you can move on with your work. Too often, there are delays in solving the problem because the support and development teams lack the necessary information to know what happened in order to diagnose, solve, and remedy the problem.

The irony is that during development, when the decision of whether to employ try catch was discussed (assuming the discussion was had), somebody argued that try catch is too expensive from a performance standpoint to warrant its use. Typically, when these conclusions are made, they’re made without any evidence to substantiate the conclusion because such a performance argument is quantified as to degree, likelihood, or cost. Ironically, in an endeavor that’s all about logic, we end up dealing with many development decisions that are gravely logically flawed. All of a sudden, it becomes crystal clear why there’s so much bad software.

In an endeavor that’s all about logic, we often make development decisions that are logically flawed.

Core Concepts

This column discusses the following JavaScript elements:

  • try: In a try block, one or more statements in that block are attempted. A block is the space that exists between curly braces ({}). At least one catch or finally statement must follow a try. The technicality that you don’t need a catch statement notwithstanding, I can’t imagine why you wouldn’t implement at least one catch statement after a try because the catch is the one place where you can intercept the thrown exception. As is the case for any code block, a try can contain nested try catch blocks.
  • catch: In the that event an exception is encountered in a try block, the catch statement immediately following the try is triggered. A catch accepts one parameter, as specified by the previous throw statement. Within a catch block, you can test for specific errors and conditionally execute code for those errors.
  • finally: While handling exceptions, you may need cleanup code that executes regardless of whether or not an exception occurs. The code in the finally block immediately following the try block always executes.
  • throw: The throw statement allows you to invoke a user-defined error. Depending on criteria you choose, you force the calling code to handle the error.
  • Error object: The Error object is a base object for generic errors and for the many built-in error types in JavaScript. In addition, the Error Object can be extended to create user-defined errors.

Be Aware of Non-standard JavaScript

Although there is the ECMAScript standard for JavaScript, it’s important to be aware of your code’s context and the non-standard features it may afford you. A good example is the conditional catch statement:

````````````catch(e if e instanceof MyError) {}

This code, which is another way to conditionally trigger a catch statement isn’t standard ECMAScript and may not work in the environment where your code executes. This is a feature that Mozilla (FireFox) created. Accordingly, this feature works in FireFox, but it won’t work in Chrome or Safari. This is what makes JavaScript challenging at times; browser-specific features that may be present. In addition, different browsers may implement new JavaScript (ECMAScript) standards at different times.

Application

A basic question to confront is when you should wrap code in a try catch block. One way to answer that question is to consider what try catch is not. Try catch’s purpose is not to remediate poor code. A common anti-pattern is to wrap code that intermittently throws an error for some unknown reason in order to apply remedial code in the catch block.

A common remedial code example includes the application of default values for variables. The problem with this implementation of try catch is that it masks what could be a serious underlying problem. Instead, try catch should be implemented in cases where errors may occur. Examples include the unavailability of an external service, invalid log-in credentials, invalid user input, etc. As much as possible, you should strive to catch specific errors. As a catch-all, it’s a good practice to have an unconditional catch block as a fail-safe in the event an error is thrown that you didn’t anticipate. An unconditional block should always be the last catch block in your statement. Catching errors allows you to log errors and, if possible, retry code so that work can progress. If such efforts are not successful, catching errors allows errors to be handled gracefully in a way that preserves the user experience. The following examples highlight some approaches you may choose to employ in your applications.

The following examples rely on this simple class:

class Result {
    constructor() {
        this.connection = null;
        this.error = null;
    }
}

Basic Try Catch, Finally, and Throw

In this next example, there’s a stub function that attempts a connection. For demonstration purposes, if a string with a length of at least one isn’t supplied, an error is thrown. In all cases, an instance of the Result class is returned. The catch block in this example is an unconditional catch in that it executes for any error. Regardless of whether an error occurs, the finally block executes.

function tryConnection(connString) {
  const result = new Result();
   try {
     if (connString == undefined ||
       connString == null ||
       connString.length == 0)
       throw new Error("You must supply a valid
          connection string.");
      result.connection = connString;
  } catch (e) {
      result.error = e;
  }
    finally {
      return result;
    }
}

Custom Error Class

The following example illustrates a simple custom error class. In the constructor, a call is made to the parent class’ constructor.

class ConnectionError extends Error {
       constructor(connString) {
   super("There was a connection problem with
          the connection string: " + connString);
   }
}

With a custom error class in place, you can now implement it. The following is a modified version of the first try catch example. The modified code is in red. Instead of throwing a generic error, the new custom error is thrown. In the catch block, there’s specific code that tests for the type of error thrown. For the custom error, the connection string is the only passed argument.

function tryConnection(connString) {
    const result = new Result();
    try {
        if (connString == undefined ||
            connString == null ||
            connString.length == 0)
            throw new ConnectionError
                    (connString);
        result.connection = connString;
    }
    catch (e) {
        if (e instanceof ConnectionError) {
            console.trace();
            console.log(e);
            // Alternatively, the console trace
            // and log code could hosted in the
            // custom error class.
        }
        result.error = e;
    }
    finally {
       return result;
   }
}

Take Note of JavaScript’s Built-in Error Definitions

JavaScript has a number of built-in error definitions. Examples include RangeError: invalid date and JSON.parse: bad parsing. Whenever possible, use built-in errors before you invest the effort into creating your own custom error class.

Key Take-aways

Errors are a fact of life in applications. The issue isn’t whether or not errors occur. Rather, the issue is how they’re handled. The key is to gain as much feedback as possible in order to apply corrective code. Different errors demand different solutions. It’s a good idea to create and implement custom errors that extend the base error class.

Try catch is not meant to be a tool that makes up for bad code.

Implementing try catch isn’t meant to be a tool to make up for bad code. If a variable isn’t supposed to be null or undefined, be sure to have unit tests that cover those scenarios. It’s far better to fix issues at the source and right when they occur than trying to address issues downstream when the prospect of recovery may be less certain.

With respect to risk management, before implementing any remedial solution, you must measure relative costs. Context matters. Ask yourself if an ounce of prevention (try catch) is worth a pound of cure (expensive after-the-fact support). If performance is claimed, quantify it. If you don’t undertake performance testing, in spite of what you may think, performance is not a real priority. Use try catch when there’s a good prospect of errors occurring. Error scenarios tend to fall into a few predictable categories that involve other resources that may not be available, such as database, cloud assets, or anything that requires access to external resources.