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.
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 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.
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.
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.
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.
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.
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:
ErrorBase type for all errors. Never actually thrown by the engine.
EvalErrorThrown when an error occurs during execution of code via eval().
RangeErrorThrown 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.
ReferenceErrorThrown when an object is expected but not available—for
instance, trying to call a method on a null reference.
SyntaxErrorTypeErrorThrown when a variable is of an unexpected type—for example,
new 10 or "prop" in true.
URIErrorThrown 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.