Create Custom JavaScript Error Objects

John Au-Yeung - Jan 30 '21 - - Dev Community

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

Subscribe to my email list now at http://jauyeung.net/subscribe/
In JavaScript, there’s an Error class to let code throw exceptions. We can create custom error classes by extending the Error class with our own class and custom logic to throw more specific errors.

The Error class has the message , name , and stack files that we inherit from. Of course, like any other class, we can define our own fields and methods inside it.

Create a New Error Class

For example, to make data validation easier, we can make a ValidationError class which is thrown whenever our code encounters invalid data.

We can create a new class as follows:

class ValidationError extends Error {
  constructor(message, type) {
    super(message);
    this.name = "ValidationError";
    this.type = type;
  }
}
Enter fullscreen mode Exit fullscreen mode

As we can see, we used the extends keyword to indicate that we want to inherit from the Error class.

Using Error Classes we Defined

After we defined our error classes, we can use our ValidationError class as follows:

try {
  let data = {
    email: 'abc'
  }
  if (!/[^@+@[^.+..+/.test(data.email)) {
    throw new ValidationError('invalid email', 'email error');
  }
} catch (ex) {
  console.log(ex);
}
Enter fullscreen mode Exit fullscreen mode

In the console.log output, we should see ‘ValidationError: invalid email’ when we run the code.

We can also log the message , name , stack , and type properties individually:

try {
  let data = {
    email: 'abc'
  }
  if (!/[^@]+@[^.]+..+/.test(data.email)) {
    throw new ValidationError('invalid email', 'email error');
  }
} catch (ex) {
  const {
    message,
    name,
    stack,
    type
  } = ex;
  console.log(
    message,
    name,
    stack,
    type
  );
}
Enter fullscreen mode Exit fullscreen mode

Then we should see:

  • invalid email logged for message 
  • ValidationError logged for name 
  • ValidationError: invalid email  at window.onload for stack
  • email error logged for type 

As with another type of object, we can use the instanceof operator to check if it’s an instance of ValidationError as follows:

try {
  let data = {
    email: 'abc'
  }
  if (!/[^@]+@[^.]+..+/.test(data.email)) {
    throw new ValidationError('invalid email', 'email error');
  }
} catch (ex) {
  if (ex instanceof ValidationError){
   console.log('validation error occurred');
  }
}
Enter fullscreen mode Exit fullscreen mode

Then we should see ‘validation error occurred’ logged since we threw an instance of the ValidationError class.

We can also look at the name property to discern different kinds of errors. For example, we can write:

try {
  let data = {
    email: 'abc'
  }
  if (!/[^@]+@[^.]+..+/.test(data.email)) {
    throw new ValidationError('invalid email', 'email error');
  }
} catch (ex) {
  if (ex.name === 'ValidationError') {
    console.log('validation error occurred');
  }
}
Enter fullscreen mode Exit fullscreen mode

Then we get the same thing as above.

Wrapping Exceptions

We can wrap lower-level exceptions, which are of instances of a subclass of a parent exception constructor and create higher-level errors.

To do this, we can catch and rethrow lower-level exceptions and catch them in higher-level code.

First, we have to create a parent and child classes which extend from the Error class as follows:

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

class ValidationError extends FormError {
  constructor(message, type) {
    super(message);
    this.name = "ValidationError";
    this.type = type;
  }
}
Enter fullscreen mode Exit fullscreen mode

In the code above, the FormError class extends the Error class and act as the parent class of the ValidationError class.

Then we write some code which makes use of our exception classes that we created:

const checkEmail = (data) => {
  try {
    if (!/[^@]+@[^.]+..+/.test(data.email)) {
      throw new ValidationError('invalid email', 'email error');
    }
  } catch (ex) {
    if (ex instanceof ValidationError) {
      throw new FormError(ex.name)
    } else {
      throw ex;
    }
  }
}

try {
  let data = {
    email: 'abc'
  }
  checkEmail(data);
} catch (ex) {
  if (ex instanceof FormError) {
    throw new FormError(ex.name)
  } else {
    throw ex;
  }
}
Enter fullscreen mode Exit fullscreen mode

In the code above, we have a checkEmail function that throws an error if the data.email property has an invalid email. If a ValidationError is thrown, then we throw a new error.

Then we create a try...catch block after the checkEmail function to wrap around the function call and then we catch the exception in the catch block. We check for FormError instances and then throw a FormError if the caught exception is an instance of the FormError .

Otherwise, we rethrow the caught exception directly. We have this pattern so that we only deal with one kind of error at the top level, instead of checking for all kinds of error in top-level code.

The alternative is to have if...else statements to check for all kinds of errors at the top-level, which is more complex.

We can inherit from the Error class to create our own error classes. Then we can throw new error instances with the throw keyword.

In the class we define, we can add our own instance fields and methods.

When we catch the errors, we can use the insranceof operator to check for the type of error that’s thrown. We can also use the name property to do the same thing.

In our lower level code, we can catch errors of type of the sub-class error type and then rethrow an error of the instance of the parent class. Then we can catch the error of the higher-level type instead of checking for error instances of the lower-level type.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .