Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Як створити власний тип помилки». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Власний тип помилки створюють класом, що успадковує `Error`: у конструкторі викликають `super(message, { cause })`, виставляють `this.name = this.constructor.name` і додають свої поля, наприклад `code`, `status` чи `details`.** Виклик `Error.captureStackTrace(this, this.constructor)` (у V8) прибирає зі стека сам конструктор, тому трасування починається з місця, де помилку кинули. Далі від базового `AppError` зручно успадкувати конкретні типи, `ValidationError`, `AuthError`, і розрізняти їх у `catch` через `instanceof`. ```javascript class AppError extends Error { constructor(message, { code, cause } = {}) { super(message, { cause }); this.name = this.constructor.name; this.code = code ?? "APP_ERROR"; } } ``` **Ключове:** `extends Error` плюс `super(message, { cause })`, власний `name` і поле `code` дають помилку, яку можна надійно розпізнати через `instanceof` і серіалізувати в лог чи відповідь API.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Власний тип помилки, це звичайний клас, що успадковує `Error`: у конструкторі ви викликаєте `super(message, { cause })`, виставляєте `this.name` і додаєте власні поля на кшталт `code`, `status` або `details`.** Такий клас поводиться як справжня помилка, зберігає стек і `cause`, і його можна точно розпізнати через `instanceof`. ## Теорія ### TL;DR - `class MyError extends Error` плюс `super(message)`, це мінімум, який уже працює. - `this.name = this.constructor.name` робить назву помилки осмисленою в логах. - Другий аргумент `super(message, { cause })` зберігає початкову помилку у ланцюжку. - `Error.captureStackTrace(this, this.constructor)` (V8) прибирає конструктор зі стека. - Власні поля (`code`, `status`, `details`) роблять помилку придатною для API і моніторингу. - Ієрархія `AppError` -> `ValidationError` / `AuthError` дає точну обробку в `catch`. ### Швидкий приклад ```javascript class ValidationError extends Error { constructor(message, details) { super(message); this.name = "ValidationError"; this.details = details; } } try { throw new ValidationError("Email is required", { field: "email" }); } catch (e) { console.log(e instanceof ValidationError, e.name, e.details); // true ValidationError { field: "email" } } ``` ### Базовий шаблон (ES2022+) ```javascript class AppError extends Error { constructor(message, { code, cause } = {}) { super(message, { cause }); // зберігає stack і cause this.name = this.constructor.name; // "AppError" this.code = code ?? "APP_ERROR"; if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } } } // Часткові випадки class ValidationError extends AppError { constructor(message, details, opts = {}) { super(message, { ...opts, code: "VALIDATION_ERROR" }); this.details = details; // будь-які ваші поля } } class AuthError extends AppError { constructor(message = "Unauthorized", opts = {}) { super(message, { ...opts, code: "AUTH_ERROR" }); this.status = 401; } } ``` Використання: ```javascript function parseUser(input) { if (!input.email) { throw new ValidationError("Email is required", { field: "email" }); } return input; } try { parseUser({}); } catch (e) { if (e instanceof ValidationError) { console.log(e.code, e.details); // VALIDATION_ERROR { field: "email" } } else { console.error("Unexpected error", e); } } ``` ### Обгортання вихідної помилки через cause Це корисно, коли ви перехоплюєте низькорівневий виняток і хочете додати контекст, не втративши оригінал: ```javascript try { JSON.parse("not valid JSON"); } catch (e) { throw new AppError("Failed to parse the config", { cause: e, code: "CONFIG_PARSE" }); } ``` Пізніше можна дивитися весь ланцюжок: ```javascript console.error(err.cause); // вихідна помилка ``` ### Версія для TypeScript ```typescript type AppErrorCode = "APP_ERROR" | "VALIDATION_ERROR" | "AUTH_ERROR"; class AppError extends Error { code: AppErrorCode; declare cause?: unknown; // щоб TS знав про cause constructor(message: string, opts: { code?: AppErrorCode; cause?: unknown } = {}) { super(message, { cause: opts.cause }); this.name = new.target.name; this.code = opts.code ?? "APP_ERROR"; if ((Error as any).captureStackTrace) { (Error as any).captureStackTrace(this, new.target); } } } class ValidationError extends AppError { details?: Record<string, unknown>; constructor(message: string, details?: Record<string, unknown>, opts: { cause?: unknown } = {}) { super(message, { ...opts, code: "VALIDATION_ERROR" }); this.details = details; } } ``` Тут `new.target.name` дає ім'я саме того класу, який створюють, тому підкласи отримують правильний `name` без зайвого коду. ### Практичні прийоми - **Серіалізація** (логування або відповідь API): ```javascript function errorToJson(err) { return { name: err.name, message: err.message, code: err.code, stack: err.stack, details: err.details, cause: err.cause instanceof Error ? err.cause.message : err.cause, }; } ``` - **Прив'язка до HTTP.** Додайте `status` (400, 401, 404, 500) у свої помилки і на рівні middleware мапте їх у відповіді. - **Гарантуйте `instanceof`.** Сучасні рушії з цим у порядку, але шаблон `extends Error` разом із `captureStackTrace` (якщо він доступний), це надійна база. Якщо ви компілюєте в ES5, додатково потрібен `Object.setPrototypeOf(this, new.target.prototype)`. ### Типові помилки - **Забути `super(message)`.** Без нього `message` буде порожнім, а стек, обрізаним. - **Не виставити `name`.** У логах і в моніторингу помилка з'явиться як звичайний `Error`, і відфільтрувати її буде нічим. - **Втратити оригінальну помилку.** Перехопили низькорівневий виняток і кинули новий без `cause`, отже причину збою вже не відновити. - **Покладатися на `e.constructor.name` замість `instanceof`.** Після мініфікації імена класів змінюються, а `instanceof` працює далі. - **Розраховувати на `Error.captureStackTrace` скрізь.** Це специфіка V8, тому виклик обов'язково треба обгорнути перевіркою наявності. - **Класти в помилку чутливі дані.** Об'єкт помилки часто повністю потрапляє в логи, тому паролям і токенам у `details` не місце.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.