Errors: throw, try and catch

Introduction

Things go wrong in every real program: a user types nonsense, a file is missing, a server is down. A good program plans for these. Error handling is what lets it say "that date is not valid" and carry on, instead of stopping with a white screen. Reading error messages calmly is also one of the most useful skills a developer can have.

How it works

When something goes wrong, JavaScript throws an error object. If nothing catches it, the program stops and the message says what happened and on which line:

ErrorUsually meansExample
SyntaxErrorthe code is not valid JavaScripta missing bracket
ReferenceErrora name that does not exist (yet)a typo in a variable name
TypeErrorthe wrong kind of valuecalling undefined() or reading a property of null
RangeErrora number out of its allowed rangenew Array(-1), endless recursion
// An error nobody catches stops the program.
const user = { name: "Rahim", address: null };
console.log("Name:", user.name);
console.log("City:", user.address.city);   // address is null
console.log("This line never runs");
Open in compiler

The visualizer stops at the line that threw and explains the error.

Catching errors. Code that may fail goes in a try block. If anything in it throws, JavaScript jumps straight to catch, which gets the error object. finally runs at the end either way, which suits clean-up work.

Throwing your own. throw new Error("message") stops the current function and hands the error to the nearest catch up the call stack. A class that extends Error gives your errors their own name, so a catch can tell them apart with instanceof:

// Your own error class, thrown and caught.
class InsufficientFunds extends Error {
  constructor(needed) {
    super(`Not enough money: need ${needed} more`);
    this.name = "InsufficientFunds";
  }
}

function withdraw(balance, amount) {
  if (amount > balance) {
    throw new InsufficientFunds(amount - balance);
  }
  return balance - amount;
}

for (const amount of [300, 900]) {
  try {
    console.log("Left:", withdraw(500, amount));
  } catch (err) {
    if (err instanceof InsufficientFunds) {
      console.log(err.message);
    } else {
      throw err;
    }
  }
}
Open in compiler

Notice how the function that finds the problem and the code that decides what to do about it can be far apart. That is the main advantage of throwing an error over returning a special value.

Syntax

try {
  riskyWork();
} catch (err) {
  console.log(err.name, err.message);
} finally {
  cleanUp();
}

throw new Error("Something is wrong");

Example

// try, catch and finally around JSON.parse.
function readAge(text) {
  try {
    const data = JSON.parse(text);
    return data.age;
  } catch {
    // No (err) here: this catch does not need the error object.
    console.log("Bad JSON, so the age is unknown");
    return null;
  } finally {
    console.log("Checked", text);
  }
}

console.log(readAge('{"age": 21}'));
console.log(readAge("{age: 21}"));
Open in compiler

Line by line

  1. readAge puts the risky JSON.parse inside try.
  2. With valid JSON, it returns data.age. Before the function actually leaves, finally prints its line.
  3. So the first call prints Checked ... before the 21 is printed by the outer console.log.
  4. The second text has no quotes around its key, so JSON.parse throws.
  5. catch prints its message and returns null, and finally still runs.

Expected result

Checked {"age": 21}
21
Bad JSON, so the age is unknown
Checked {age: 21}
null

In the real world

Every network request can fail, so a good app shows "Could not load, please try again" instead of breaking. Servers catch errors for each request, so that one bad request cannot bring down the service for everybody else.

Common mistakes

  • An empty catch that hides every error. At least print it.
  • Wrapping a whole program in one try. Catch errors where you can do something useful about them.
  • Throwing a string, as in throw "bad". Throw an Error object, which records the message and where it happened.
  • Expecting try to catch a SyntaxError in the same script. A script with a syntax error never starts.

Best practices

  • Check input early, and throw a clear message when it is wrong.
  • Re-throw any error your catch does not know how to handle.
  • Write messages that say what went wrong and what was expected.
  • Read the error's name, its message and its line number before you change any code.

Summary

Errors are objects that are thrown and travel up the call stack until something catches them. try and catch handle them, and finally always runs. Your own error classes let a catch block respond differently to different problems.

Quick check

1. Which error does reading a property of undefined throw?
2. When does a finally block run?
3. What happens to the rest of a try block after a line throws?
4. How can a catch block tell your InsufficientFunds error from others?

Key takeaways

  • An uncaught error stops the program.
  • Catch an error only where you can do something useful about it.
  • Throw Error objects, not strings.

Practice problems for this lesson

My notes

Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.