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.

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
Errorand set theirname. - An error can carry extra data and its cause in
{ cause }. instanceoftells error types apart incatch.- Unknown errors should be rethrown.
finallyalways 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.