Chapter 14. Exception Handling
Table of contents
Buy the book
(Ad, please don’t block.)

Chapter 14. Exception Handling

This chapter describes how JavaScript’s exception handling works. It begins with a general explanation of what exception handling is.

What Is Exception Handling?

In exception handling, you often group statements that are tightly coupled. If, while you are executing those statements, one of them causes an error, then it makes no sense to continue with the remaining statements. Instead, you try to recover from the error as gracefully as you can. This is loosely reminiscent of transactions (but without the atomicity).

Let’s look at code without exception handling:

function processFiles() {
    var fileNames = collectFileNames();
    var entries = extractAllEntries(fileNames);
    processEntries(entries);
}
function extractAllEntries(fileNames) {
    var allEntries = new Entries();
    fileNames.forEach(function (fileName) {
        var entry = extractOneEntry(fileName);
        allEntries.add(entry);  // (1)
    });
}
function extractOneEntry(fileName) {
    var file = openFile(fileName);  // (2)
    ...
}
...

What is the best way to react to an error in openFile() at (2)? Clearly, the statement (1) should not be executed anymore. But we wouldn’t want to abort extractAllEntries(), either. Instead, it is enough to skip the current file and continue with the next one. To do that, we add exception handling to the previous code:

function extractAllEntries(fileNames) {
    var allEntries = new Entries();
    fileNames.forEach(function (fileName) {
        try {
            var entry = extractOneEntry(fileName);
            allEntries.add(entry);
        } catch (exception) {  // (2)
            errorLog.log('Error in '+fileName, exception);
        }
    });
}
function extractOneEntry(fileName) {
    var file = openFile(fileName);
    ...
}
function openFile(fileName) {
    if (!exists(fileName)) {
        throw new Error('Could not find file '+fileName); // (1)
    }
    ...
}

There are two aspects to exception handling:

  1. If there is a problem that can’t be handled meaningfully where it occurs, throw an exception.
  2. Find a place where errors can be handled: catch exceptions.

At (1), the following constructs are active:

    processFile()
        extractAllEntries(...)
            fileNames.forEach(...)
                function (fileName) { ... }
                    try { ... } catch (exception) { ... }
                        extractOneEntry(...)
                            openFile(...)

The throw statement at (1) walks up that tree and leaves all constructs until it encounters an active try statement. It then invokes that statement’s catch block and passes it the exception value.

Exception Handling in JavaScript

Exception handling in JavaScript works like in most programming languages: a try statement groups statements and lets you intercept exceptions in those statements.

throw

The syntax of throw is as follows:

throw «value»;

Any JavaScript value can be thrown. For simplicity’s sake, many JavaScript programs just throw strings:

// Don't do this
if (somethingBadHappened) {
    throw 'Something bad happened';
}

Don’t do this. JavaScript has special constructors for exception objects (see Error Constructors). Use those or subclass them (see Chapter 28). Their advantage is that JavaScript automatically adds a stack trace (on most engines) and that they have room for additional context-specific properties. The simplest solution is to use the built-in constructor Error():

if (somethingBadHappened) {
    throw new Error('Something bad happened');
}

try-catch-finally

The syntax of try-catch-finally looks as follows. try is mandatory, and at least one of catch and finally must be there, too:

try {
    «try_statements»
}
catch («exceptionVar») {
   «catch_statements»
}
finally {
   «finally_statements»
}

Here’s how it works:

Examples

Any value can be thrown:

function throwIt(exception) {
    try {
        throw exception;
    } catch (e) {
        console.log('Caught: '+e);
    }
}

Here is the interaction:

> throwIt(3);
Caught: 3
> throwIt('hello');
Caught: hello
> throwIt(new Error('An error happened'));
Caught: Error: An error happened

finally is always executed:

function throwsError() {
    throw new Error('Sorry...');
}
function cleansUp() {
    try {
        throwsError();
    } finally {
        console.log('Performing clean-up');
    }
}

Here is the interaction:

> cleansUp();
Performing clean-up
Error: Sorry...

finally is executed after a return statement:

function idLog(x) {
    try {
        console.log(x);
        return 'result';
    } finally {
        console.log("FINALLY");
    }
}

Here is the interaction:

> idLog('arg')
arg
FINALLY
'result'

The return value is queued before executing finally:

var count = 0;
function countUp() {
    try {
        return count;
    } finally {
        count++;  // (1)
    }
}

By the time statement (1) is executed, the value of count has already been queued for returning:

> countUp()
0
> count
1

Error Constructors

ECMAScript standardizes the following error constructors. The descriptions are quoted from the ECMAScript 5 specification:

Here are the properties of errors:

message
The error message.
name
The name of the error.
stack
A stack trace. This is nonstandard, but is available on many platforms—for example, Chrome, Node.js, and Firefox.

Stack Traces

The usual sources of errors are either external (wrong input, missing file, etc.) or internal (a bug in the program). Especially in the latter case, you will get unexpected exceptions and need to debug. Often you don’t have a debugger running. For “manual” debugging, two pieces of information are helpful:

  1. Data: What values do variables have?
  2. Execution: In what line did the exception happen, and what function calls were active?

You can put some of the first item (data) into either the message or the properties of an exception object. The second item (execution) is supported on many JavaScript engines via stack traces, snapshots of the call stack when the exception objects were created. The following example prints a stack trace:

function catchIt() {
    try {
        throwIt();
    } catch (e) {
        console.log(e.stack); // print stack trace
    }
}
function throwIt() {
    throw new Error('');
}

Here’s the interaction:

> catchIt()
Error
    at throwIt (~/examples/throwcatch.js:9:11)
    at catchIt (~/examples/throwcatch.js:3:9)
    at repl:1:5
Next: 15. Functions