Thursday, June 8, 2017

A Guide to Proper Error Handling in JavaScript

Ah, the perils of error handling in JavaScript. If you believe Murphy’s law, anything that can go wrong, will go wrong. In this article, I would like to explore error handling in JavaScript. I will cover pitfalls, good practices, and finish with asynchronous code and Ajax.

This popular article was updated on 08.06.2017 to address reader feedback. Specifically, file names were added to snippets, unit tests were cleaned up, wrapper pattern was added to uglyHandler, sections on CORS and 3rd party error handlers were added.

I feel JavaScript’s event-driven paradigm adds richness to the language. I like to imagine the browser as this event-driven machine, and errors are no different. When an error occurs, an event gets thrown at some point. In theory, one could argue errors are simple events in JavaScript.

If this sounds foreign to you, buckle up as you are in for quite a ride. For this article, I will focus only on client-side JavaScript.

This topic builds on concepts explained in Exceptional Exception Handling in JavaScript. I recommend reading up on the basics if you are not familiar. This article also assumes an intermediate level of JavaScript knowledge. If you're looking to level up, why not sign up for SitePoint Premium and watch our course JavaScript: Next Steps. The first lesson is free.

In either case, my goal is to explore beyond the bare necessities for handling exceptions. Reading this article will make you think twice the next time you see a nice try...catch block.

The Demo

The demo we’ll be using for this article is available on GitHub, and presents a page like this:

Error Handling in JavaScript Demo

All buttons detonate a “bomb” when clicked. This bomb simulates an exception that gets thrown as a TypeError. Below is the definition of such a module:

// scripts/error.js

function error() {
  var foo = {};
  return foo.bar();
}

To begin, this function declares an empty object named foo. Note that bar() does not get a definition anywhere. Let’s verify that this will detonate a bomb with a good unit test:

// tests/scripts/errorTest.js

it('throws a TypeError', function () {
  should.throws(error, TypeError);
});

This unit test is in Mocha with test assertions in Should.js. Mocha is a test runner while Should.js is the assertion library. Feel free to explore the testing APIs if you are not already familiar. A test begins with it('description') and ends with a pass / fail in should. The unit tests run on Node and do not need a browser. I recommend paying attention to the tests as they prove out key concepts in plain JavaScript.

Once you have cloned the repo and installed the dependencies, you can run the tests using npm t. Alternatively, you can run this individual test like so: ./node_modules/mocha/bin/mocha tests/scripts/errorTest.js.

As shown, error() defines an empty object then it tries to access a method. Because bar() does not exist within the object, it throws an exception. Believe me, with a dynamic language like JavaScript this happens to everyone!

The Bad

On to some bad error handling. I have abstracted the handler on the button from the implementation. Here is what the handler looks like:

// scripts/badHandler.js

function badHandler(fn) {
  try {
    return fn();
  } catch (e) { }
  return null;
}

This handler receives a fn callback as a parameter. This callback then gets called inside the handler function. The unit tests show how it is useful:

// tests/scripts/badHandlerTest.js

it('returns a value without errors', function() {
  var fn = function() {
    return 1;
  };

  var result = badHandler(fn);

  result.should.equal(1);
});

it('returns a null with errors', function() {
  var fn = function() {
    throw new Error('random error');
  };

  var result = badHandler(fn);

  should(result).equal(null);
});

As you can see, this bad error handler returns null if something goes wrong. The callback fn() can point to a legit method or a bomb.

The click event handler below tells the rest of the story:

// scripts/badHandlerDom.js

(function (handler, bomb) {
  var badButton = document.getElementById('bad');

  if (badButton) {
    badButton.addEventListener('click', function () {
      handler(bomb);
      console.log('Imagine, getting promoted for hiding mistakes');
    });
  }
}(badHandler, error));

Continue reading %A Guide to Proper Error Handling in JavaScript%


by Camilo Reyes via SitePoint

No comments:

Post a Comment