JavaScript - Extending Errors

Hello, future JavaScript wizards! Today, we're going to dive into the exciting world of extending errors in JavaScript. Don't worry if you're new to programming – I'll be your friendly guide through this adventure. By the end of this lesson, you'll be creating your own custom errors like a pro!

JavaScript - Extending Errors

Extending the Error Class: Creating Custom Errors

Let's start with the basics. In JavaScript, we have a built-in Error class that we can use to create error objects. But sometimes, we need more specific errors for our applications. That's where extending the Error class comes in handy!

Why Extend Errors?

Imagine you're building a cooking app, and you want to create specific errors for kitchen mishaps. You could use the generic Error, but wouldn't it be nice to have a BurnedFoodError or OverseasonedError? That's what we're going to learn to do!

Basic Syntax for Extending Errors

Let's start with a simple example:

class KitchenError extends Error {
  constructor(message) {
    super(message);
    this.name = 'KitchenError';
  }
}

Let's break this down:

  1. We use the class keyword to define our new error class.
  2. extends Error tells JavaScript that our new class should inherit from the built-in Error class.
  3. In the constructor, we call super(message) to make sure the parent Error class is initialized properly.
  4. We set this.name to give our error a specific name.

Now, let's see how we can use this:

try {
  throw new KitchenError("The spaghetti is stuck to the ceiling!");
} catch (error) {
  console.log(error.name); // Outputs: KitchenError
  console.log(error.message); // Outputs: The spaghetti is stuck to the ceiling!
}

Adding Custom Properties

One of the coolest things about extending errors is that we can add our own custom properties. Let's enhance our KitchenError:

class KitchenError extends Error {
  constructor(message, dish) {
    super(message);
    this.name = 'KitchenError';
    this.dish = dish;
  }
}

try {
  throw new KitchenError("It's burning!", "lasagna");
} catch (error) {
  console.log(`Oh no! The ${error.dish} is in trouble: ${error.message}`);
  // Outputs: Oh no! The lasagna is in trouble: It's burning!
}

In this example, we added a dish property to our error. This allows us to provide more context about what exactly went wrong in our kitchen disaster!

Creating Specific Error Types

Now that we know how to extend the Error class, let's create some specific error types for our kitchen app:

class BurnedFoodError extends KitchenError {
  constructor(dish) {
    super(`The ${dish} is burned to a crisp!`, dish);
    this.name = 'BurnedFoodError';
  }
}

class OverseasonedError extends KitchenError {
  constructor(dish, seasoning) {
    super(`The ${dish} is over-seasoned with ${seasoning}!`, dish);
    this.name = 'OverseasonedError';
    this.seasoning = seasoning;
  }
}

Now we can use these specific error types in our code:

function cookDinner(dish, seasoning) {
  if (Math.random() < 0.5) {
    throw new BurnedFoodError(dish);
  } else if (Math.random() < 0.5) {
    throw new OverseasonedError(dish, seasoning);
  }
  console.log(`Your ${dish} is perfectly cooked!`);
}

try {
  cookDinner("steak", "salt");
} catch (error) {
  if (error instanceof BurnedFoodError) {
    console.log(`Oops! ${error.message} Time to order takeout.`);
  } else if (error instanceof OverseasonedError) {
    console.log(`Yikes! ${error.message} Maybe use less ${error.seasoning} next time.`);
  } else {
    console.log("Something went wrong in the kitchen!");
  }
}

This code simulates the unpredictable nature of cooking (at least for some of us!) and shows how we can handle different types of errors in different ways.

Multilevel Inheritance

Now, let's take our error hierarchy to the next level – literally! We can create a chain of error types, each inheriting from the previous one. This is called multilevel inheritance.

Let's expand our kitchen error system:

class KitchenApplianceError extends KitchenError {
  constructor(message, appliance) {
    super(message);
    this.name = 'KitchenApplianceError';
    this.appliance = appliance;
  }
}

class OvenError extends KitchenApplianceError {
  constructor(message) {
    super(message, 'oven');
    this.name = 'OvenError';
  }
}

class MicrowaveError extends KitchenApplianceError {
  constructor(message) {
    super(message, 'microwave');
    this.name = 'MicrowaveError';
  }
}

In this example:

  • KitchenApplianceError extends KitchenError
  • OvenError and MicrowaveError both extend KitchenApplianceError

Let's see how we can use this hierarchy:

function useAppliance(appliance) {
  if (appliance === 'oven') {
    throw new OvenError("The oven won't heat up!");
  } else if (appliance === 'microwave') {
    throw new MicrowaveError("The microwave is making strange noises!");
  }
}

try {
  useAppliance('oven');
} catch (error) {
  if (error instanceof OvenError) {
    console.log(`Oven problem: ${error.message}`);
  } else if (error instanceof MicrowaveError) {
    console.log(`Microwave issue: ${error.message}`);
  } else if (error instanceof KitchenApplianceError) {
    console.log(`General appliance error with the ${error.appliance}: ${error.message}`);
  } else if (error instanceof KitchenError) {
    console.log(`Kitchen error: ${error.message}`);
  } else {
    console.log(`Unexpected error: ${error.message}`);
  }
}

This multilevel inheritance allows us to create very specific error types while still maintaining a logical hierarchy. We can catch errors at different levels of specificity, from the most specific (OvenError) to the most general (Error).

Methods Table

Here's a table summarizing the key methods and properties we've used in our custom errors:

Method/Property Description Example
constructor() Initializes the error object constructor(message, dish)
super() Calls the parent class constructor super(message)
this.name Sets the name of the error this.name = 'KitchenError'
this.[custom] Adds a custom property this.dish = dish
instanceof Checks if an object is an instance of a class if (error instanceof OvenError)

Remember, extending errors is not just about creating fancy names for your mistakes – it's about creating a structured way to handle different types of errors in your code. This can make debugging easier and your error messages more informative and specific.

So, the next time you're coding and something goes wrong, don't just throw a generic error – create a custom one! Who knows, maybe your CodeSpaghetti Error will become the talk of your development team. Happy coding, and may all your errors be perfectly extended!

Credits: Image by storyset