Javascript – Debugging and Error Handling

Before going into examples, let’s make sure that we know some basics. I highlighted four types of errors in JavaScript:

Syntax errors occur at the interpretation time, they are considered as bugs and can never be handled properly because the code is just wrong by definition:

  var x = 2;
  var y = 3;

  console.log(x + y;

2. Program error, which is also a bug. For example, trying to read a property of undefined — it can be avoided by improving the code in this particular case by adding a conditional statement.

   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)?

3. Runtime errors are called exceptions which happen during the execution:

  • invalid user input
  • failed to connect to a database
  • system out of memory
  • etc.
  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.

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])
      })
    })
  }

There are quite a number of ways and methods to deal with errors and exceptions.

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.

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. 

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.

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

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. 

var log = "";

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

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

  function errorHandler(error) {
    log += "H";
  }
  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. 

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 one, 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.