Exception Handling in JavaScript

The try-catch

As with many programming languages, the primary method of dealing with exceptions in JavaScript is the try-catch. In a nutshell, the try-catch is a code block that can be used to deal with thrown exceptions without interrupting program execution. In other words, you can "try" to execute a block of code, and "catch" any exceptions that are thrown.

Catching all exceptions

At its simplest, catching exceptions in JavaScript requires simply wrapping an expression in a try-catch statement. Without any additional configuration, this method will catch any and all exceptions that get thrown.

try {
  // ...
} catch (e) {
  // ...
}

While this is relatively simplistic in terms of error handling, it is important to note that the exception that is caught is always an implementation of the JavaScript Error object, which brings with it some useful properties such as a human-readable description of the error. This allows you to log these exceptions for debugging.

try {
  // ...
} catch (e) {
  console.log(e);
}

Catching specific exceptions

In most instances, however, catching every exception thrown is considered bad practice. It is far more manageable to specifically catch and react to exceptions that you expect to encounter, rather than implementing a catchall. To accomplish this, simply check the exception instance type before reacting to it.

try {
  // ...
} catch (e) {
   if ( e instanceof CustomExceptionError ) {
    // ...
  }
}

While JavaScript does offer the ability to add conditionals directly within the catch definition, this behavior is considered non-standard, so the more reliable way to implement conditional catch clauses is to use a conditional block within the catch to check and react to the error instance. With this in mind, you can see how a simple try-catch can be expanded into a much more complex workflow, allowing you to deal with errors in a host of different ways.

try {
  // ...
} catch (e) {
 if ( e instanceof CustomExceptionError ) {
    // ...
  } else if ( e instanceof OtherExceptionError ) {
    // ...
  } else {
    // ...
  }
}

The window.onError() method

Even though wrapping every piece of code we write in a try-catch block would certainly deal with unexpected errors, it isn't sustainable, and still opens the application up to unplanned-for issues. In a browser-based application, a global onError event handler is available that can be used as a global catchall for any errors that arise.

window.onError = function(message, source, lineno, colno, error) {
  // ...
}

While you can utilize this method in a number of different ways, it is generally best used for logging and display formatting, as every exception thrown will be caught—not just built-in exceptions—which can result in unpredictable behavior if not used properly.

Using **uncaughtException** in Node.js

But what about something like Node.js? Obviously, since there is no window in Node.js, there can't be a window.onError() method. Implemented in a similar way to an unconditional try-catch, but as a global catchall, Node's uncaughtException is a great last resort for cleanly dealing with any uncaught exceptions before halting program execution.

process.on('uncaughtException', (err) => {
  // ...
});

As mentioned above, global catchalls can be overly generalistic for proper error handling, however with the addition of error codes in Node.js catching and responding to the most common errors in one place can be done in a safe and manageable way within uncaughtException. To do this, introducing conditional logic around the code into the handler will allow you to deal with all failed assertions or buffer overflows exactly the same, regardless of where these errors are thrown in the code.

process.on('uncaughtException', (err) => {
  if (error.code === 'ERR_BUFFER_OUT_OF_BOUNDS' ) {
    // ...
  } else *if (error.code === *'ERR_ASSERTION' ) {
    // ...
  } else {
    // ...
  }
});

Exceptions in AngularJS

While vanilla JavaScript has built-in error handling, many JavaScript frameworks have their own methodology for throwing and handling exceptions. AngularJS 1.x, for example, offers the $exceptionHandler service, which acts as a default exception handler for any uncaught exceptions, similarly to the browser's own window.onError() handler.

The default behavior of Angular's exception handler is to simply log the exception to the console; however, this can be extended by intercepting any errors that pass through the handler, and passing them onto a new service for additional functionality.

app.provider(
  "$exceptionHandler",
  {
    $get: function( customExceptionService ) {
      return( customExceptionService );
    }
  }
);

app.factory(
  "errorLogService",
  function( $log, $window, stacktraceService ) {
    function log( exception, cause ) {
        // ...
    }  
    return( log );
  }
);

In Angular 2.x, this behavior has been simplified with the introduction of the ErrorHandler provider. As with Angular 1.x, the default behavior of the error handler is to simply pass the errors directly to the console, however by extending the handleError method additional tasks (such as additional logging) can be performed as well.

class MyErrorHandler implements ErrorHandler {
  handleError(error) {
    // do something with the exception
    super.handleError(error);
  }
}

@NgModule({
  providers: [{provide: ErrorHandler, useClass: MyErrorHandler}]
})
class MyModule {}

Learn more in our blog Custom Error Handling for Angular.

Exceptions in React

React treats error handling ... differently. While try-catch works in a lot of non-vanilla JavaScript applications, it only works for imperative code. Because React components are declarative, try-catch isn't a reliable option. To compensate for this, React implements what are called error boundaries. Error boundaries are React components that "catch JavaScript errors anywhere in their child component tree" while also logging the errors and displaying a fallback user interface.

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  componentDidCatch(error, info) {
    // Display fallback UI
    this.setState({ hasError: true });
    // You can also log the error to an error reporting service
    logErrorToMyService(error, info);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

With an error boundary defined, it can then be used as if it were any other component:

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

See more examples in our blog on React error boundaries.