Custom Errors: создание собственных типов ошибок

Зачем нужны свои ошибки? 🤔

Стандартные ошибки JavaScript (Error, SyntaxError, TypeError) — это здорово, но иногда их не хватает. Представь, что ты пишешь библиотеку для работы с платежами. Какой смысл использовать общие ошибки, если можно создать свою — PaymentError?

Свои ошибки делают код:

  • Понятнее – сразу ясно, что пошло не так
  • Удобнее для отладки – можно добавить специфичные свойства
  • Профессиональнее – это уровень senior-разработчика!
// Вместо этого:
throw new Error('Недостаточно средств')

// Делаем так:
throw new PaymentError('Недостаточно средств', { userId: 123, amount: 500 })

Базовый пример: создаём класс ошибки 🏗️

Создадим простейшую кастомную ошибку, наследуясь от Error:

class ValidationError extends Error {
  constructor(message) {
    super(message) // передаём message в родительский класс
    this.name = 'ValidationError' // имя ошибки
  }
}

// Используем
try {
  throw new ValidationError('Некорректный email')
} catch (err) {
  console.log(err.name)    // ValidationError
  console.log(err.message) // Некорректный email
  console.log(err.stack)   // стек вызовов сохраняется!
}

💡 Всегда задавай this.name — это важно для отладки! Без него в err.name будет просто "Error".


Расширяем функционал: добавляем контекст 🔍

Главная фишка кастомных ошибок — возможность добавлять любые дополнительные данные. Допустим, мы валидируем форму:

class FormValidationError extends Error {
  constructor(message, field, value) {
    super(message)
    this.name = 'FormValidationError'
    this.field = field   // какое поле не прошло валидацию
    this.value = value   // какое значение было передано
  }
}

// Пример использования
function validateAge(age) {
  if (age < 18) {
    throw new FormValidationError('Возраст меньше 18', 'age', age)
  }
}

try {
  validateAge(15)
} catch (err) {
  if (err instanceof FormValidationError) {
    console.error(`Ошибка в поле ${err.field}: ${err.message}`)
    console.error(`Переданное значение: ${err.value}`)
  }
}

Проверка типа ошибки: instanceof vs err.name 🔎

Есть два способа проверить тип ошибки:

try {
  // какой-то код
} catch (err) {
  // Способ 1 (предпочтительный!)
  if (err instanceof ValidationError) {
    // обрабатываем именно ValidationError
  }

  // Способ 2
  if (err.name === 'ValidationError') {
    // тоже работает, но менее надёжно
  }
}

⚠️ instanceof — более надёжный вариант, особенно если учитывать наследование и кросс-оконные сценарии (например, iframe'ы).


Продвинутый пример: иерархия ошибок 🏰

Настоящая магия начинается, когда ты строишь целую систему ошибок. Вот как может выглядеть структура для API:

// Базовый класс для всех API-ошибок
class ApiError extends Error {
  constructor(message, statusCode) {
    super(message)
    this.name = this.constructor.name
    this.statusCode = statusCode || 500
  }
}

// Ошибка "Не найдено"
class NotFoundError extends ApiError {
  constructor(resource) {
    super(`Ресурс ${resource} не найден`, 404)
    this.resource = resource
  }
}

// Ошибка авторизации
class UnauthorizedError extends ApiError {
  constructor(message = 'Требуется авторизация') {
    super(message, 401)
  }
}

// Использование
fetch('/user/123')
  .then(response => {
    if (!response.ok) {
      throw new NotFoundError('Пользователь')
    }
  })
  .catch(err => {
    if (err instanceof NotFoundError) {
      showToast(`Ошибка: ${err.message}`)
    }
  })

Лучшие практики 💎

  1. Наследуйся от Error – это даст стек вызовов и все стандартные свойства
  2. Используй this.name – иначе теряется смысл кастомной ошибки
  3. Добавляй контекст – любые дополнительные данные для отладки
  4. Создавай иерархию – базовый класс ошибки для приложения + наследники
  5. Логируй разумно – не забывай про error.stack в production
// Идеальный пример
class DatabaseError extends Error {
  constructor(message, query, params) {
    super(message)
    this.name = 'DatabaseError'
    this.query = query    // проблемный SQL-запрос
    this.params = params  // параметры запроса
    this.timestamp = new Date()
  }
}

Когда не стоит создавать свои ошибки 🚧

Не нужно изобретать велосипед, если:

  • Ошибка совсем общая (лучше использовать new Error())
  • Ты пишешь маленькую утилиту без сложной логики
  • В команде нет конвенции по именованию ошибок

📺 Подробнее о тонкостях работы с ошибками смотри в курсе Данилы Бежина: https://www.youtube.com/@DanilaBezhin (разбор реальных кейсов из production!)

Скрыть рекламу навсегда

📘 VK Видео — обучение без ограничений

Все уроки доступны без VPN, без блокировок и зависаний.

Можно смотреть с телефона, планшета или компьютера — в любое время.

▶️ Смотреть на VK Видео