Proper Error Handling in JavaScript

Chris Nwamba

Every programmer out there wants to write code that works. In the long run, chances of your program running as expected reduce due to one thing ‒ errors. Errors in JavaScript could get complex at certain times and when that happens, programmers have no one to blame but the themselves or the language itself. It would be great if we all knew how to detect these errors, expose them and prevent them from happening again. One thing to note though, this article assumes some knowledge in JavaScript. If you’re looking to enhance your skills, why not sign up with Scotch and watch our course on JavaScript. It’s completely free. My objective is to show you how JavaScript errors can be handled properly and avoided in your code. When you are done, you won’t be scared of frequently implementing features such as the stack property as well as handling errors.

Categories of Errors

Usually JavaScript errors can be divided into two categories, actual problems and programmer mistakes. Actual problems are events that are not preventable by the programmer. For example, a program asking a user to enter a name and it gets back an empty string.

Programmer mistakes are errors made by the programmer when writing programs. They can be further subdivided into:

Syntax Errors

These are the most basic kind of errors. Also known as parsing errors, they occur at compilation time in traditional programming languages and interpretation time in JavaScript. Check out the code block below:

  var x = 2;
  var y = 3;

  console.log(x + y;

In the example above, the last line of code will cause a syntax error because it is missing a closing parenthesis. Usually when a syntax error occurs in JavaScript, the rest of the code in other threads will get executed if they contain nothing that depends on the code containing the error.

Runtime Errors

Also known as exceptions, these kind of errors occur when your program is executed, usually when it has been compiled or interpreted. Consider the code block below:

  var windowObject;
  var windowFeatures = "menubar=yes,location=yes,resizable=yes,scrollbars=yes,status=yes";

  function openPopup() {
    windowObject = window.openObject("http://www.bbc.com/", "BBC_WindowName", strWindowFeatures);
  }

The function above will cause a runtime error because although the syntax is correct, at runtime it is trying to call a method openObject() that doesn’t exist.

Logical Errors

The most complex of all three errors, logical errors occur when you make a mistake or flaw in the logic that controls your program’s script. This mistake causes the program to alter expected results or exhibit unexpected behavior. It’s usually very tricky trying to catch and even correct logical errors, consider the example below:

  function power(base, exponent) {
    var result = 1;
    for (var count = 0; count < exponent; count++)
      result *= base;
    return result;
  }

What if someone tries to call power (``"``JavaScript``"``, 10)? Well that’s obviously a programmer mistake. What about power (5, 0.2)? A quick look at the function’s logic will tell you that it can’t handle fractional exponents but by the law of Mathematics, raising a number to the halfth power is possible. JavaScript does this via the Math.pow() function. In such situations where it’s not entirely clear what kind of input a function accepts, explicitly stating the kind of arguments that are acceptable in a comment or input validation becomes a good idea.

Best Practices for Handling Errors

One common criticism of JavaScript is the way errors are handled through callbacks. Check out the pattern in which the code below is written:

  var myFunc = function(cb) {
    doSomething(function (err, a) {
      if (err) return cb(err)
      doSomethingElse(function (err, b) {
        if (err) return cb(err)
        return cb(null, [a, b])
      })
    })
  }

What’s good about this kind of pattern? It forces programmers to handle errors. As the person writing the code, you always want to make sure you know when an operation can fail, especially if it’s an asynchronous operation.

What’s bad about this kind of pattern? It seems like a lazy way of writing code. Asides that, if part of the code fails, the entire operation will fail. Certain exceptions to this are retry logic, reverting changes, and advanced error reporting.

Fortunately, there are quite a number of ways and methods to deal with errors and exceptions. Let’s check out these methods and how they can be applied to our code:

The try…catch…finally Statement

The try…catch…finally statement marks a block of statements to run in your code and specifies a response should an exception be thrown. It s possible to catch logical and runtime errors but not syntax errors. Below is the syntax for the try…catch…finally statement:

  try {
    // Code to run
    [
      break;
    ]
  } catch (e) {
    // Code to run if an exception occurs
    [
      break;
    ]
  }
  [
    finally {
      // Code that is always executed regardless of 
      // an exception occurring
    }
  ]

try statements are the statements to be executed. If an exception occurs during the execution of the try statement, the exception is placed in e and the catch clause is executed otherwise, the catch clause is skipped. The finally clause executes after the try statement is finished, it executes regardless of whether or not an exception was thrown or caught. try statements can be used either with just the catch clause, just the finally clause or both. Check out this demo using just the try statement and the catch clause:

Using the finally clause allows you to execute an additional command after try…catch. Here’s an example:

The Throw Statement

The throw statement is used to generate user-defined exceptions. During runtime, when a throw statement is encountered, execution of the current function will stop and control will be passed to the first catch clause in the call stack. If there is no catch clause, the program will terminate. Check out this example showing how to use a throw statement:

The onerror() Method

The onerror() method was the first event handler to facilitate and handle errors in JavaScript. It is often used with the syntax window.onerror. This enables the error event to be fired on the window object whenever an error occurs during runtime. Below is an example showing how to use the onerror() method:

Another utility mode for onerror() is using it to display an error message in case there is any error when loading images in your site:

  <img src="coolPhoto.jpg" onerror="alert('An error occurred loading yor photo.')" />

Call Stack Property

The stack property is a feature in JavaScript Error object. It offers a trace of which functions were called, in what order, from which line and file and with what arguments, proceeding from the most recent calls to earlier ones all the way to the original global scope call. Check out this code block demonstrating the stack property:

  function trace() {
    try {
      throw new Error('myError');
    } catch (e) {
      alert(e.stack);
    }
  }

  function b() {
    trace();
  }

  function a() {
    b(3, 4, '\n\n', undefined, {});
  }
  a('first call, firstarg');

Assuming the above markup is saved as C:\stackoverflow.js on a Windows file system it produces an alert message box with the following text:

  trace@file:///C:/stackoverflow.js:4:17
  b@file:///C:/stackoverflow.js:11:13
  a@file:///C:/stackoverflow.js:14:13
  @file:///C:/stackoverflow.js:15:9

A warning though, stack is a non standard feature. It shouldn’t be used on production sites facing the web as it will not work for every user.

Handling Errors in Asynchronous Code

Usually errors in asynchronous code require a large amount of if… else checks and a careful inspection of parameter values. Promises allow asynchronous code to apply structured error handling. When using promises, you can process errors by passing an error handler to the then method or using a catch clause. Just like exceptions in regular code, an exception or rejection in asynchronous code will jump to the nearest error handler. Check out the code block below:

  var log = "";

  function doWork() {
    log += "W";
    return Promise.resolve();
  }

  function doError() {
    log += "E";
    throw new Error("oops!");
  }

  function errorHandler(error) {
    log += "H";
  }

These functions were created to show you how to process errors using the then method. We’ll use them with the following code:

  doWork()
    .then(doWork)
    .then(doError)
    .then(doWork) // this will be skipped
    .then(doWork, errorHandler)
    .then(verify);

  function verify() {
    expect(log)
      .toBe("This");
    done();
  }

What’s expected is that the log variable will contain “WWEH” when the code finishes executing, meaning the flow of calls with reach doWork , then doWork, then doError, then errorHandler. There are two things we can observe from this. The first is that when the call to doError throws an exception, execution jumps to the next rejection handler which is errorHandler and skips over any potential success handlers. This behavior is obvious once you think of promises as a tool to transform asynchronous code into a procedural flow of method calls. In synchronous code, an exception will jump over statements and up the stack to find a catch handler, and the asynchronous code in this example is no different.

The second observation is that the verify function will execute as a success handler after the error. Just like normal execution can resume in procedural code after a catch clause, normal execution can resume with promises after a handled error. The verify function executes because the error handler returns a successfully resolved promise. Remember that it’s the then method’s job to return a new promise, and unless the error handler explicitly rejects a new promise, the new promise resolves successfully.

A promise object also provides a catch clause to handle errors. Check out this example which is written using a catch clause:

  doWork()
    .then(doWork)
    .then(doError)
    .then(doWork)
    .then(doWork)
    .catch(errorHandler)
    .then(verify);

The catch clause takes only a rejection handler method. There can be a difference in behavior between the following two code snippets:

  .then(doWork, errorHandler)

… and …

  .then(doWork)
  .catch(errorHandler)

In the first snippet, if the success handler throws an exception or rejects a promise, execution will not go into the error handler since the promise was already resolved at this level. With catch, you can always see an error that was not handled from the previous success handler. Finally, imagine you have a rejected promise in your code, but there is no error handler attached. You can simulate this scenario with the following line of code:

  Promise.reject("error!");

Some native environments and promise polyfills will warn you about unhandled promise rejections by displaying a message in the console of the developer tools. An unhandled promise rejection could spell doom your application as it might be leaving a critical error unattended to.

Conclusion

When it comes to handling errors as a programmer, you either choose to ignore them and play the pretend game or become a superhero and go back in time to save the world - or in this case, your code. Personally I would recommend becoming a superhero, ignoring errors will only lead to more and more errors until the entire stack becomes impossible to work on or even to attempt a bug fix. There’s no harm in trying, no shame in admitting failure. There will always be mistakes, it’s what we do about them that matters in the end.

Chris Nwamba

48 posts

JavaScript Preacher. Building the web with the JS community.