Chapter 10. Throw Your Own Errors

When I was younger, the most befuddling part of programming languages was the ability to create errors. My first reaction to the throw operator in Java was, “Well, that’s stupid; why would you ever want to cause an error?” Errors were my enemy—something I sought to avoid—so the ability to cause an error seemed like a useless and dangerous aspect of the language. I thought it was dumb to include the same operator in JavaScript, a language that people just didn’t understand in the first place. Now, with a great deal of experience under my belt, I’m a big fan of throwing my own errors.

Throwing errors in JavaScript is an art. It takes time to feel out where the appropriate parts of your code should throw errors. Once you figure this out, however, you’ll find that your debugging time will decrease and your satisfaction with the code will increase.

The Nature of Errors

An error occurs in programming when something unexpected happens. Maybe the incorrect value was passed into a function or a mathematical operation had an invalid operand. Programming languages define a base set of rules that when deviated from, result in errors so that the developer can fix the code. Debugging would be nearly impossible if errors weren’t thrown and reported back to you. If everything failed silently, it would take you a long time to notice that there was an issue in the first place, let alone isolate and fix it. Errors are the friends of developers, not enemies.

The problem with errors is that they tend to pop up in unexpected places and at unexpected times. To make matters worse, the default error messages are usually too terse to really explain what went wrong. JavaScript error messages are notoriously uninformative and cryptic (especially in old versions of Internet Explorer), which only compounds the problem. Imagine if an error popped up with a message that said, “This function failed because this happened.” Instantly, your debugging task becomes easier. This ease is the advantage of throwing your own errors.

It helps to think of errors as built-in failure cases. It’s always easier to plan for a failure at a particular point in code than it is to anticipate failure everywhere. This is a very common practice in product design, not just in code. Cars are built with crumple zones, areas of the frame that are designed to collapse in a predictable way when impacted. Knowing how the frame will react in a crash—specifically, which parts will fail—allows the manufacturers to ensure passenger safety. Your code can be constructed in the same way.

Throwing Errors in JavaScript

Throwing errors in your JavaScript is arguably more valuable than in any other language due to the complexities involved in web debugging. You can throw an error by using the throw operator and providing an object to throw. Any type of object can be thrown; however, an Error object is the most typical to use:

throw new Error("Something bad happened.")

The built-in Error type is available in all JavaScript implementations, and the constructor takes a single argument, which is the error message. When you throw an error in this way, and the error isn’t caught via a try-catch statement, the browser displays the value of message in the browser’s typical way. Most browsers now have a console to which error information is output whenever an error occurs. In other words, any error you throw is treated the same way as an error that you didn’t throw.

Inexperienced developers sometimes throw errors by just providing a string, such as:

// Bad
throw "message";

Doing so will cause an error to be thrown, but not all browsers respond the way you’d expect. Firefox, Opera, and Chrome each display an “uncaught exception” message and then include the message string. Safari and Internet Explorer simply throw an “uncaught exception” error and don’t provide the message string at all, which isn’t very useful for debugging purposes.

Of course, you can throw any type of data that you’d like. There are no rules prohibiting specific data types:

throw { name: "Nicholas" };
throw true;
throw 12345;
throw new Date();

The only thing to remember is that throwing any value will result in an error if it’s not caught via a try-catch statement. Firefox, Opera, and Chrome all call String() on the value that was thrown to display something logical as the error message; Safari and Internet Explorer do not. The only surefire way to have all browsers display your custom error message is to use an Error object.

Advantages of Throwing Errors

Throwing your own error allows you to provide the exact text to be displayed by the browser. Instead of just line and column numbers, you can include any information that you’ll need to successfully debug the issue. I recommend that you always include the function name in the error message as well as the reason why the function failed. Consider the following function:

function getDivs(element) {
    return element.getElementsByTagName("div");
}

This function’s purpose is to retrieve all <div> elements that are a descendant of element. It’s quite common for functions that interact with the DOM to be passed null values where DOM elements should be. What happens if null is passed to this function? You’ll get a cryptic error message such as “object expected.” Then you’ll need to look at the execution stack to actually locate the source of the problem. Debugging becomes much easier by throwing an error:

function getDivs(element) {

    if (element && element.getElementsByTagName) {
        return element.getElementsByTagName("div");
    } else {
        throw new Error("getDivs(): Argument must be a DOM element.");
    }
}

Now that getDivs() throws an error, any time element doesn’t meet the criteria for continuing, an error is thrown that very clearly states the problem. If this error shows up in the browser console, you know immediately where to start debugging and that the most likely cause is a call to retrieve a DOM element is returning null at some point.

I like to think of throwing errors as leaving sticky notes for myself as to why something failed.

When to Throw Errors

Understanding how to throw errors is just one part of the equation; understanding when to throw errors is the other. Because JavaScript doesn’t have type or argument checking, a lot of developers incorrectly assume that they should implement these types of checking for every function. Doing so is impractical and can adversely affect the script’s overall performance. Consider this function, which tries to implement overly aggressive type checking:

// Bad: Too much error checking
function addClass(element, className) {
    if (!element || typeof element.className != "string") {
        throw new Error("addClass(): First argument must be a DOM element.");
    }

    if (typeof className != "string") {
        throw new Error("addClass(): Second argument must be a string.");
    }

    element.className += " " + className;
}

This function simply adds a CSS class to a given element; however, most of the function is taken up doing error checking. Even though it may be tempting to check each argument in every function (mimicking statically typed languages), doing so is often overkill in JavaScript. The key is to identify parts of the code that are likely to fail in a particular way and throw errors only there. In short, throw errors only where errors will already occur.

The most likely cause of an error in the previous example is a null reference being passed in to the function. If the second argument is null, or a number, or a boolean, no error will be thrown, because JavaScript will coerce the value into a string. That may mean that the resulting display of the DOM element isn’t as expected, but it certainly doesn’t rise to the level of serious error. So I would check only for the DOM element, as in:

// Good
function addClass(element, className) {
    if (!element || typeof element.className != "string") {
        throw new Error("addClass(): First argument must be a DOM element.");
    }

    element.className += " " + className;
}

If a function is only ever going to be called by known entities, error checking is probably unnecessary (this is the case with private functions); if you cannot identify all the places where a function will be called ahead of time, then you’ll likely need some error checking and will even more likely benefit from throwing your own errors. The best place for throwing errors is in utility functions, such as the addClass() function, that are a general part of the scripting environment and may be used in any number of places, which is precisely the case with JavaScript libraries.

All JavaScript libraries should throw errors from their public interfaces for known error conditions. Large libraries such as jQuery, YUI, and Dojo can’t possibly anticipate when and where you’ll be calling their functions. It’s their job to tell you when you’re doing stupid things, because you shouldn’t have to debug into library code to figure out what went wrong. The call stack for an error should terminate in the library’s interface and no deeper. There’s nothing worse than seeing an error that’s 12 functions deep into a library; library developers have a responsibility to prevent this from happening.

The same goes for private JavaScript libraries. Many web applications have their own proprietary JavaScript libraries either built with or in lieu of the well-known public options. The goal of libraries is to make developers’ lives easier, which is done by providing an abstraction away from the dirty implementation details. Throwing errors helps to keep those dirty implementation details hidden safely away from developers.

Some good general rules of thumb for throwing errors:

  • Once you’ve fixed a hard-to-debug error, try to add one or two custom errors that can help you more easily solve the problem, should it occur again.

  • If you’re writing code and think, “I hope [something] doesn’t happen—that would really mess up this code,” then throw an error when that something occurs.

  • If you’re writing code that will be used by people you don’t know, think about how they might incorrectly use the function and throw errors in those cases.

Remember that the goal isn’t to prevent errors—it’s to make errors easier to debug when they occur.

The try-catch Statement

JavaScript provides a try-catch statement that is capable of intercepting thrown errors before they are handled by the browser. The code that might cause an error comes in the try block and code that handles the error goes into the catch block. For instance:

try {
    somethingThatMightCauseAnError();
} catch (ex) {
    handleError(ex);
}

When an error occurs in the try block, execution immediately stops and jumps to the catch block, where the error object is provided. You can inspect this object to determine the best course of action to recover from the error.

There is also a finally clause that can be added. The finally clause contains code that will be executed regardless of whether an error occurs. For example:

try {
    somethingThatMightCauseAnError();
} catch (ex) {
    handleError(ex);
} finally {
    continueDoingOtherStuff();
}

The finally clause is a little bit tricky to work with in certain situations. For example, if the try clause contains a return statement, it won’t actually return until finally has been evaluated. Due to this trickiness, finally is used infrequently, but it is a powerful tool for error handling if necessary.

Throw or try-catch?

Typically, developers have trouble discerning whether it’s appropriate to throw an error or catch one using try-catch. Errors should be thrown only in the deepest part of the application stack, which, as discussed previously, typically means JavaScript libraries. Any code that handles application-specific logic should have error-handling capabilities and should therefore be catching errors thrown from the lower-level components.

Application logic always knows why it was calling a particular function and is therefore best suited for handling the error. Never have a try-catch statement with an empty catch clause; you should always be handling errors in some way. For example, never do this:

// Bad
try {
    somethingThatMightCauseAnError();
} catch (ex) {
    // noop
}

If you know an error might happen, then you should also know how to recover from that error. Exactly how you recover from the error may be different in development mode as opposed to what actually gets put into production, and that’s okay. The important thing is that you’re actually handling the error, not just ignoring it.

Error Types

ECMA-262 specifies seven error object types. These are used by the JavaScript engine when various error conditions occur and can also be manually created:

Error

Base type for all errors. Never actually thrown by the engine.

EvalError

Thrown when an error occurs during execution of code via eval().

RangeError

Thrown when a number is outside the bounds of its range—for example, trying to create an array with –20 items (new Array(-20)). These errors rarely occur during normal execution.

ReferenceError

Thrown when an object is expected but not available—for instance, trying to call a method on a null reference.

SyntaxError

Thrown when the code passed into eval() has a syntax error.

TypeError

Thrown when a variable is of an unexpected type—for example, new 10 or "prop" in true.

URIError

Thrown when an incorrectly formatted URI string is passed into encodeURI, encodeURIComponent, decodeURI, or decodeURIComponent.

Understanding that there are different types of errors can make it easier to handle them. All error types inherit from Error, so checking the type with instanceof Error doesn’t give you any useful information. By checking for the more specific error types, you get more robust error handling:

try {
    // something that causes an error
} catch (ex) {
    if (ex instanceof TypeError) {
        // handle the error
    } else if (ex instanceof ReferenceError) {
        // handle the error
    } else {
        // handle all others
    }
}

If you’re throwing your own errors, and you’re throwing a data type that isn’t an error, you can more easily tell the difference between your own errors and the ones that the browser throws. There are, however, several advantages to throwing actual Error objects instead of other object types.

First, as mentioned before, the error message will be displayed in the browser’s normal error-handling mechanism. Second, the browser attatches extra information to Error objects when they are thrown. These vary from browser to browser, but they provide contextual information for the error such as line number and column number and, in some browsers, stack and source information. Of course, you lose the ability to distinguish between your own errors and browser-thrown ones if you just use the Error constructor.

The solution is to create your own error type that inherits from Error. Doing so allows you to provide additional information as well as distinguish your errors from the errors that the browser throws. You can create a custom error type using the following pattern:

function MyError(message) {
    this.message = message;
}

MyError.prototype = new Error();

There are two important parts of this code: the message property, which is necessary for browsers to know the actual error string, and setting the prototype to an instance of Error, which identifies the object as an error to the JavaScript engine. Now you can throw an instance of MyError and have the browser respond as if it’s a native error:

throw new MyError("Hello world!");

The only caveat to this approach is that Internet Explorer 8 and earlier won’t display the error message. Instead, you’ll see the generic “Exception thrown but not caught” error message. The big advantage of this approach is that custom error objects allow you to test specifically for your own errors:

try {
    // something that causes an error
} catch (ex) {
    if (ex instanceof MyError) {
        // handle my own errors
    } else {
        // handle all others
    }
}

If you’re always catching any errors you throw, then Internet Explorer’s slight stupidity shouldn’t matter all that much. The benefits from such an approach are huge in a system with proper error handling. This approach gives you much more flexibility and information for determining the correct course of action for a given error.