Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Custom error classes

The built-in Error is enough to report a problem, but it does not always say which problem it is. An invalid email, a missing product and a network failure call for different reactions. Custom error classes let you tell them apart.

An error class

An error is an object of the Error class, so your own error is created by inheritance. Set the name property to the class name — then it shows up in messages:

class ValidationError extends Error {
  name = 'ValidationError';
}
 
const error = new ValidationError('Invalid email');
 
console.log(error.name);    // ValidationError
console.log(error.message); // Invalid email

You do not need a constructor: the message is accepted and stored by the parent Error constructor.

Telling errors apart

The instanceof operator checks which class an error belongs to. This way one catch can handle different problems differently:

class ValidationError extends Error {
  name = 'ValidationError';
}
 
function checkAge(age) {
  if (age < 0) {
    throw new ValidationError('Age cannot be negative');
  }
  return age;
}
 
try {
  checkAge(-5);
} catch (error) {
  if (error instanceof ValidationError) {
    console.log(`Please check the data: ${error.message}`); // Please check the data: Age cannot be negative
  }
}

Extra data

You can add properties to your error — for example, the name of the form field that is wrong. For this, write a constructor and call super(message) first:

class ValidationError extends Error {
  name = 'ValidationError';
 
  constructor(field, message) {
    super(message);
    this.field = field;
  }
}
 
const error = new ValidationError('email', 'Wrong format');
 
console.log(`${error.field}: ${error.message}`); // email: Wrong format

Rethrowing

If catch gets an error it cannot handle, it throws it again with throw. Then the error reaches code that knows what to do with it:

class ValidationError extends Error {
  name = 'ValidationError';
}
 
function handle(action) {
  try {
    action();
  } catch (error) {
    if (error instanceof ValidationError) {
      return 'Bad data';
    }
    throw error;
  }
}
 
try {
  handle(() => {
    throw new TypeError('Unexpected failure');
  });
} catch (error) {
  console.log(error.name); // TypeError
}

The cause of an error

Sometimes one error is caused by another. As the second argument of Error you can pass an object with a cause field — the original error. Then the whole chain is visible when you investigate the problem:

try {
  try {
    JSON.parse('{ broken }');
  } catch (error) {
    throw new Error('Could not load the settings', { cause: error });
  }
} catch (error) {
  console.log(error.message);    // Could not load the settings
  console.log(error.cause.name); // SyntaxError
}

Common mistake

You can throw any value, even a string, but you should not. A string has no name, message or call stack — it does not tell you where the problem happened:

try {
  throw 'Something went wrong';
} catch (error) {
  console.log(error.message); // undefined
}

Always throw Error objects or objects of your own subclasses.

Thanks for reading!

Donate to the site — we work hard for you.