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
instanceofto tell your custom error apart from other errors
💡 Why this matters: So far you’ve only caught errors JavaScript throws for you, like a
TypeErrorfrom 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.throwis 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.
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:
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:
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
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:
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.
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:
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
throwraises an error on purpose and immediately stops the current function; always throw anErrorobject, not a plain string.- A custom error class extends
Errorusing the sameclass/extends/super()syntax from Module 7, and should setthis.namein its constructor. instanceoflets acatchblock 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.