How to create a custom error class in JavaScript

How to create a custom error class in JavaScript

Creating a custom error class in JavaScript can provide meaningful error handling that goes beyond the default Error object. By defining a structure that encapsulates specific properties and methods, you can tailor error messages and behavior to fit your application’s needs.

class CustomError extends Error {
  constructor(message, code) {
    super(message);
    this.name = this.constructor.name;
    this.code = code;
    Error.captureStackTrace(this, this.constructor);
  }
}

This code defines a CustomError class that extends the built-in Error class. The constructor takes a message and a code, setting these properties on the instance. The call to Error.captureStackTrace ensures that the stack trace is captured accurately, making debugging easier.

When you want to throw this custom error, you can do so in a way that provides clear context about the error’s nature. For example:

function someFunction() {
  // some code that may fail
  throw new CustomError("Something went wrong!", 404);
}

Here, when someFunction is called and an error occurs, it throws a CustomError with a descriptive message and an error code. This allows the calling code to handle errors more intelligently, distinguishing between different types of errors based on the code or message.

try {
  someFunction();
} catch (error) {
  if (error instanceof CustomError) {
    console.error(CustomError: ${error.message} (Code: ${error.code}));
  } else {
    console.error(General Error: ${error.message});
  }
}

In this catch block, you can see how to differentiate between a CustomError and a general error. This is important for implementing error handling logic this is appropriate for the specific circumstances of the error.

Additionally, you may want to include more properties in your custom error class to provide even richer context. For instance, you might want to track the original input or conditions that led to the error:

class ExtendedError extends CustomError {
  constructor(message, code, input) {
    super(message, code);
    this.input = input;
  }
}

This ExtendedError class takes an additional parameter, input, which you can use to log the data that caused the error. This can be invaluable during debugging sessions.

function anotherFunction(userInput) {
  if (!isValid(userInput)) {
    throw new ExtendedError("Invalid input provided!", 400, userInput);
  }
}

By including the input that triggered the error, you gain insight into what went wrong, which can significantly streamline the debugging process and improve your application’s robustness. Custom error handling becomes not just a mechanism for signaling failure, but a comprehensive part of your application’s architecture, allowing for clear, actionable responses to different error scenarios.

Implementing stack traces and error messages

To further enhance your custom error handling, consider implementing a mechanism for attaching additional metadata to your errors. This can include information such as timestamps, user IDs, or other contextual data that can aid in diagnosing issues.

class MetadataError extends CustomError {
  constructor(message, code, metadata) {
    super(message, code);
    this.metadata = metadata;
    this.timestamp = new Date();
  }
}

In this MetadataError class, we introduce a metadata object that can hold any relevant information about the error’s context. The timestamp property records when the error occurred, providing a chronological reference that can be useful in tracking down issues over time.

When throwing a MetadataError, you can pass in an object that contains various details:

function processRequest(userId) {
  if (!userId) {
    throw new MetadataError("User ID is required!", 400, { userId, requestTime: new Date() });
  }
}

This approach allows you to encapsulate not only the error message and code but also important contextual information about the request that led to the error. Such granularity can significantly enhance your debugging capabilities, especially in a production environment.

When catching these errors, you can log the metadata alongside the error message, providing a fuller picture of what went wrong:

try {
  processRequest(null);
} catch (error) {
  if (error instanceof MetadataError) {
    console.error(MetadataError: ${error.message} (Code: ${error.code}));
    console.error(Metadata:, error.metadata);
    console.error(Timestamp: ${error.timestamp});
  } else {
    console.error(General Error: ${error.message});
  }
}

This catch block not only handles the error but also logs the additional metadata, enabling better insights into the conditions surrounding the failure. Such practices can lead to more efficient troubleshooting and a better understanding of your application’s behavior under different circumstances.

Lastly, consider implementing a method for serializing your custom errors. This can be particularly useful if you need to send error information over a network or log it to an external service. Serialization can help maintain the integrity of the error data while ensuring it’s easily consumable.

class SerializableError extends CustomError {
  toJSON() {
    return {
      name: this.name,
      message: this.message,
      code: this.code,
      stack: this.stack,
      timestamp: new Date(),
    };
  }
}

With the toJSON method, you can easily convert your SerializableError instances into JSON format, which can be sent over the network or stored in logs. This approach enhances the usability of your custom error classes, making them more versatile in different contexts.

By combining custom error classes with metadata and serialization capabilities, you create a robust error handling framework that not only signals problems but also provides rich context and actionable information for resolving those issues efficiently.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *