Skip to Content
Course content

236: Creating Custom Error Subclasses

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was reviewing a pull request for a payment processing module. The developer had wrapped everything in a massive try-catch block, and every single failure—whether it was a timed-out API call, an expired credit card, or a database deadlock—was thrown as a generic Error. When it hit the catch block, the code just logged "An error occurred" and returned a 500 status. We spent three days digging through logs just to figure out that a specific third-party vendor was rejecting a specific currency code. If we had been throwing specific error types, the catch block could have instantly identified the "CurrencyNotSupportedError" and responded with a helpful 400 Bad Request instead of a generic crash.

The Power of Type-Checking Your Failures

When you throw a basic new Error('Something went wrong'), you're essentially throwing a string with a stack trace. The problem is that once that error lands in a catch block, you have no programmatic way to distinguish it from any other error except by parsing the error message string—which is a fragile nightmare because one typo in a string literal can break your entire error-handling logic.

By creating a subclass of the built-in Error class, you give your errors a "type." This allows you to use the instanceof operator to handle different failure scenarios gracefully. I always tell my team: don't just tell the system that something failed; tell the system what failed.

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

try {
  throw new ValidationError("Invalid email format");
} catch (err) {
  if (err instanceof ValidationError) {
    console.log("User input error: " + err.message);
  } else {
    console.log("Unexpected system error: " + err.message);
  }
}

Cleaning Up the Prototype and Name

You'll notice in the example above that I manually set this.name = "ValidationError". If you skip this, the error will still function, but when you log it to the console, it will just say "Error: Invalid email format" instead of "ValidationError: Invalid email format". That small detail saves you an immense amount of time when scanning through a thousand lines of production logs.

In some older environments or specific transpilation setups, you might find that the prototype chain gets a bit wonky when extending built-in classes. While modern JavaScript handles this well, you might occasionally see developers manually setting the prototype. For 99% of modern projects, simply calling super(message) and assigning the name property is all you need to keep your stack traces intact and your debugging sanity preserved.

I usually recommend creating a dedicated errors.js file in your project. Instead of scattering custom classes throughout your business logic, define all your domain-specific errors in one place. This makes it incredibly easy for other developers on your team to see exactly what failure modes they need to account for when calling your functions.




📋 Practical Task

Build a Specialized User Authentication Error System

You are building a login system that needs to distinguish between different types of authentication failures so the UI can show specific messages (e.g., "Wrong password" vs "Account locked").

Your Task:

  • Create a base class called AuthError that extends the built-in Error class.
  • Create two subclasses that extend AuthError: InvalidCredentialsError and AccountLockedError.
  • Ensure each subclass correctly sets its name property to match the class name.
  • Write a function authenticate(user) that:
    • Throws an InvalidCredentialsError if user.password is not "secret123".
    • Throws an AccountLockedError if user.isLocked is true.
  • Wrap the function call in a try-catch block and use instanceof to log a different message for each specific error type, and a generic message for any other unexpected errors.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.