JavaScript for impatient programmers
Please support this book: buy it or donate
(Ad, please don’t block.)

22 Exception handling



This chapter covers how JavaScript handles exceptions.

  Why doesn’t JavaScript throw exceptions more often?

JavaScript didn’t support exceptions until ES3. That explains why they are used sparingly by the language and its standard library.

22.1 Motivation: throwing and catching exceptions

Consider the following code. It reads profiles stored in files into an Array with instances of class Profile:

function readProfiles(filePaths) {
  const profiles = [];
  for (const filePath of filePaths) {
    try {
      const profile = readOneProfile(filePath);
      profiles.push(profile);
    } catch (err) { // (A)
      console.log('Error in: '+filePath, err);
    }
  }
}
function readOneProfile(filePath) {
  const profile = new Profile();
  const file = openFile(filePath);
  // ··· (Read the data in `file` into `profile`)
  return profile;
}
function openFile(filePath) {
  if (!fs.existsSync(filePath)) {
    throw new Error('Could not find file '+filePath); // (B)
  }
  // ··· (Open the file whose path is `filePath`)
}

Let’s examine what happens in line B: An error occurred, but the best place to handle the problem is not the current location, it’s line A. There, we can skip the current file and move on to the next one.

Therefore:

When we throw, the following constructs are active:

readProfiles(···)
  for (const filePath of filePaths)
    try
      readOneProfile(···)
        openFile(···)
          if (!fs.existsSync(filePath))
            throw

One by one, throw exits the nested constructs, until it encounters a try statement. Execution continues in the catch clause of that try statement.

22.2 throw

This is the syntax of the throw statement:

throw «value»;

Any value can be thrown, but it’s best to throw an instance of Error or its subclasses.

throw new Error('Problem!');

22.2.1 Options for creating error objects

22.3 The try statement

The maximal version of the try statement looks as follows:

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

You can combine these clauses as follows:

Since ECMAScript 2019, you can omit the catch parameter (error), if you are not interested in the value that was thrown.

22.3.1 The try block

The try block can be considered the body of the statement. This is where we execute the regular code.

22.3.2 The catch clause

If an exception reaches the try block, then it is assigned to the parameter of the catch clause and the code in that clause is executed. Next, execution normally continues after the try statement. That may change if:

The following code demonstrates that the value that is thrown in line A is indeed caught in line B.

const errorObject = new Error();
function func() {
  throw errorObject; // (A)
}

try {
  func();
} catch (err) { // (B)
  assert.equal(err, errorObject);
}

22.3.3 The finally clause

The code inside the finally clause is always executed at the end of a try statement – no matter what happens in the try block or the catch clause.

Let’s look at a common use case for finally: You have created a resource and want to always destroy it when you are done with it, no matter what happens while working with it. You’d implement that as follows:

const resource = createResource();
try {
  // Work with `resource`. Errors may be thrown.
} finally {
  resource.destroy();
}
22.3.3.1 finally is always executed

The finally clause is always executed, even if an error is thrown (line A):

let finallyWasExecuted = false;
assert.throws(
  () => {
    try {
      throw new Error(); // (A)
    } finally {
      finallyWasExecuted = true;
    }
  },
  Error
);
assert.equal(finallyWasExecuted, true);

And even if there is a return statement (line A):

let finallyWasExecuted = false;
function func() {
  try {
    return; // (A)
  } finally {
    finallyWasExecuted = true;
  }
}
func();
assert.equal(finallyWasExecuted, true);

22.4 Error classes

Error is the common superclass of all built-in error classes. It has the following subclasses (I’m quoting the ECMAScript specification):

22.4.1 Properties of error objects

Consider err, an instance of Error:

const err = new Error('Hello!');
assert.equal(String(err), 'Error: Hello!');

Two properties of err are especially useful:

  Exercise: Exception handling

exercises/exception-handling/call_function_test.mjs

  Quiz

See quiz app.