DMarlon Studio

Lesson 16 · 16 of 20

Custom errors and finally · JavaScript

Create your own JavaScript error classes with extends Error, chain the original cause and always release resources with finally.

JavaScript · Custom errors and finally in DMarlon Studio: the example's code and its output in the terminal

To tell one failure from another, create your own error classes that extend Error. AppError is the application's base: it calls super(message, options) and sets name to the real class name with this.constructor.name, so every subclass introduces itself as ValidationError or PaymentError with no repeated code.

Subclasses can carry extra data: ValidationError stores the failing field in field. PaymentError needs nothing but its name, so class PaymentError extends AppError {} is enough. The second argument of Error, { cause }, chains the original error: the declined payment keeps the technical reason in err.cause.

In the catch, `instanceof` decides what to do with each kind of error: a message about the field, or a declined-payment notice. Errors it does not recognise are rethrown with throw err, so unexpected failures such as programming bugs are not hidden.

The `finally` block always runs, whether try finishes normally or throws, and even if there is a return inside. That is why every order prints closed: it is the place to release resources, close files or connections and leave everything tidy.

Code · main.js

class AppError extends Error {  constructor(message, options) {    super(message, options); // options can carry { cause }    this.name = this.constructor.name; // "ValidationError", "PaymentError"...  }}class ValidationError extends AppError {  constructor(field, message) {    super(message);    this.field = field;  }}class PaymentError extends AppError {}function charge(order) {  if (!order.email) throw new ValidationError("email", "an email is required");  if (order.amount > 500) {    throw new PaymentError(`order ${order.id} declined`, { cause: new Error("card limit is 500 EUR") });  }  return `Order ${order.id} paid: ${order.amount} EUR`;}const orders = [{ id: 1, email: "ana@shop.test", amount: 80 }, { id: 2, amount: 20 }, { id: 3, email: "luis@shop.test", amount: 900 }];for (const order of orders) {  try {    console.log(charge(order));  } catch (err) {    if (err instanceof ValidationError) console.log(`${err.name} in "${err.field}": ${err.message}`);    else if (err instanceof PaymentError) console.log(`${err.name}: ${err.message} (${err.cause.message})`);    else throw err; // unknown errors keep travelling up  } finally {    console.log(`  order ${order.id} closed`); // runs whether it failed or not  }}

Output · node main.js

Order 1 paid: 80 EUR
  order 1 closed
ValidationError in "email": an email is required
  order 2 closed
PaymentError: order 3 declined (card limit is 500 EUR)
  order 3 closed

Key points

  • Custom error classes extend Error and set their name.
  • An error can carry extra data and its cause in { cause }.
  • instanceof tells error types apart in catch.
  • Unknown errors should be rethrown.
  • finally always runs, error or not.

Exercise

Create a StockError class with an item property, throw it from charge when the order has item: "lamp", and handle it in the catch.