CodingNic

Error Handling

Throwing and Custom Errors

Error Handling 18 min read

Throwing and Custom Errors

Objectives

By the end of this lesson, you should be able to:

  • Raise your own errors with throw
  • Write a custom error class by extending JavaScript’s built-in Error
  • Use instanceof to tell your custom error apart from other errors

💡 Why this matters: So far you’ve only caught errors JavaScript throws for you, like a TypeError from a bad method call. Real programs need to raise their own errors too, for things JavaScript has no way of knowing are wrong, like a negative age or a withdrawal larger than an account’s balance. throw is how you signal that, and a custom error class is how you make the signal specific enough for the calling code to act on.

throw Stops Execution Immediately

throw raises an error on purpose. Like any other error, it immediately stops the current function and looks for a catch block to handle it.

javascript
function validateAge(age) {
  if (age < 0) {
    throw new Error("Age cannot be negative");
  }
  return age;
}

try {
  validateAge(-5);
} catch (error) {
  console.log("Caught an error:", error.message);
}

console.log(validateAge(30));

Output:

text
Caught an error: Age cannot be negative
30

The line return age; inside validateAge(-5) never runs, because throw exits the function the moment it executes. Always throw an actual Error object (new Error("...")), not a plain string. Error objects carry a .message, a .name, and a stack trace; a thrown string has none of that.

The Problem With Only Using Error

new Error("...") works, but every error you throw looks identical to the code catching it: same name, "Error". If a function can fail for several different reasons, the caller has no clean way to tell them apart other than reading the message text.

Custom Error Classes

You already know how to build a class that extends another one, from class/extends/super() in Module 7. A custom error class uses that exact same pattern, extending the built-in Error class instead of one of your own:

javascript
class InsufficientFundsError extends Error {
  constructor(message) {
    super(message);
    this.name = "InsufficientFundsError";
  }
}

super(message) passes the message up to Error’s own constructor, the same way you’d call super() to reach any parent class’s constructor. Setting this.name matters: without it, the error’s name stays the generic "Error" inherited from the parent class, even though the class itself is named InsufficientFundsError.

Throwing and Catching a Custom Error

javascript
class InsufficientFundsError extends Error {
  constructor(message) {
    super(message);
    this.name = "InsufficientFundsError";
  }
}

function withdraw(balance, amount) {
  if (amount > balance) {
    throw new InsufficientFundsError(
      "Cannot withdraw " + amount + " from balance of " + balance
    );
  }
  return balance - amount;
}

try {
  withdraw(50, 100);
} catch (error) {
  console.log("name:", error.name);
  console.log("message:", error.message);
}

console.log("New balance:", withdraw(50, 20));

Output:

text
name: InsufficientFundsError
message: Cannot withdraw 100 from balance of 50
New balance: 30

error.name and error.message both carry exactly the information the class was built to provide: which kind of error it was, and the specific detail about this particular failure.

Telling Errors Apart With instanceof

The real payoff of a custom error class is that calling code can check what kind of error it caught, using instanceof, and react differently depending on the answer.

javascript
class InsufficientFundsError extends Error {
  constructor(message) {
    super(message);
    this.name = "InsufficientFundsError";
  }
}

function withdraw(balance, amount) {
  if (amount > balance) {
    throw new InsufficientFundsError("Not enough funds");
  }
  return balance - amount;
}

try {
  withdraw(50, 100);
} catch (error) {
  console.log("is InsufficientFundsError:", error instanceof InsufficientFundsError);
  console.log("is Error:", error instanceof Error);
}

Output:

text
is InsufficientFundsError: true
is Error: true

Both checks are true. Because InsufficientFundsError extends Error, every InsufficientFundsError is also an Error; instanceof respects that inheritance chain, the same way it would for any other class hierarchy. This lets a catch block handle InsufficientFundsError specifically, while still treating it as a normal error everywhere else.

Try It

Write a custom error class called NegativeAmountError that extends Error and sets this.name to "NegativeAmountError". Then write a function deposit(balance, amount) that throws a NegativeAmountError if amount is less than 0, and otherwise returns balance + amount. Test it with both a negative and a positive amount, and print error.name, error.message, and error instanceof NegativeAmountError from the catch block.

Recap

  • throw raises an error on purpose and immediately stops the current function; always throw an Error object, not a plain string.
  • A custom error class extends Error using the same class/extends/super() syntax from Module 7, and should set this.name in its constructor.
  • instanceof lets a catch block tell your custom error apart from other errors, so it can handle exactly the failures it knows how to handle.

Next lesson covers best practices: what to actually catch, why an empty catch block is dangerous, and when to let an error propagate instead of handling it.