Создание пользовательских ошибок в JavaScript
Создание пользовательских ошибок в JavaScript
Технический фундамент throw и иерархии ошибок
С технической точки зрения throw прерывает текущий путь выполнения и передает управление ближайшему обработчику. Для проектного кода важно строить иерархию ошибок:
- системные ошибки (
TypeError,ReferenceError) обычно сигнализируют о баге; - доменные ошибки (
ValidationError,AuthError) описывают ожидаемые проблемные сценарии; - обработка через
instanceofпозволяет разделить реакции по типам.
Фундаментальный принцип: ошибка должна быть типизированным сигналом, а не случайной строкой.
Зачем создавать свои ошибки
Стандартные ошибки вроде TypeError полезны, но часто они слишком общие для бизнес-задач. Когда ты пишешь продуктовый код, важно различать типы сбоев: неверный email, недоступный тариф, просроченный токен, пустая корзина. Пользовательские ошибки позволяют явно описать, что именно пошло не так.
Ключевой момент: пользовательская ошибка это способ сделать поведение системы предсказуемым для тебя, команды и логирования.
Проверь себя: почему сообщение "Validation failed" хуже, чем "Email обязателен" в практической отладке?
throw как явный сигнал о проблеме
throw прерывает нормальное выполнение и выбрасывает исключение. Ты можешь бросать как встроенные ошибки, так и свои.
function requireEmail(email) {
if (!email) {
throw new Error('Email обязателен');
}
return email.trim().toLowerCase();
}
Смотри, что важно: throw используется, когда продолжать выполнение в текущем состоянии нельзя или опасно. Это не инструмент для "обычной ветки".
Проверь себя: когда лучше вернуть null, а когда бросить ошибку?
Минимальная практика: разные сообщения для разных причин
function validatePassword(password) {
if (typeof password !== 'string') {
throw new TypeError('Пароль должен быть строкой');
}
if (password.length < 8) {
throw new RangeError('Пароль слишком короткий');
}
return true;
}
Здесь мы используем разные типы ошибок:
TypeErrorдля неверного типа;RangeErrorдля недопустимой длины.
Такой подход помогает и в коде, и в обработке ошибок: можно по типу выбрать нужное сообщение для UI.
Кастомный класс ошибки
Когда ошибок много, удобно создавать свой класс через class ... extends Error.
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
function validateUser(user) {
if (!user.email) {
throw new ValidationError('Email обязателен', 'email');
}
}
Новый термин: кастомный класс ошибки - свой тип исключения с дополнительными полями, например field, code, details.
Проверь себя: зачем явно задавать this.name, если класс уже называется ValidationError?
Подсказка: потому что super(message) создает Error и по умолчанию name часто остается равным 'Error'.
Плюс в сборках/минификации имя класса может меняться, а name хочется стабильным для логов и обработки.
class ValidationError2 extends Error {}
console.log(new ValidationError2('oops').name); // Error
Обработка пользовательских ошибок через instanceof
Обычно в catch ты отличаешь "ожидаемые" бизнес-ошибки от системных сбоев.
try {
validateUser({ email: '' });
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Поле ${error.field}: ${error.message}`);
} else {
console.log('Неожиданная ошибка, нужен отдельный разбор');
}
}
Дополнительный пример: передача машинно-читаемого code в кастомной ошибке.
class ApiError extends Error {
constructor(message, code) {
super(message);
this.name = 'ApiError';
this.code = code;
}
}
Здесь часто путаются: не все ошибки нужно показывать пользователю как есть. Внутренние ошибки лучше логировать, а пользователю отдавать безопасное, понятное сообщение.
Реальный микро-сценарий
Сценарий: регистрация пользователя.
validateInputбросаетValidationError, если email/пароль невалидны;- UI показывает конкретные подсказки по полям;
- неожиданные ошибки (например, баг в коде) идут в лог и показывают общий текст "Попробуй позже".
Это разделение резко повышает качество UX и скорость дебага.
Частые ошибки новичков
- Бросать строку (
throw 'error') вместо объектаError. - Делать одинаковый тип ошибки для разных причин.
- Перехватывать все и терять первичную ошибку.
- Использовать
throwтам, где можно безопасно вернуть результат проверки.
Анти-провал: всегда задавай ошибке четкое сообщение и, при необходимости, машинно-удобный код причины (code).
Что будет, если изменить входные данные
- Если в
validatePasswordпередать число, сработаетTypeError. - Если передать строку длиной 5, будет
RangeError. - Если строка длиной 10, функция пройдет без ошибок.
Это и есть предсказуемое поведение: одна входная ситуация -> один тип реакции.
Проверь себя: почему это важно для автотестов и стабильной API-логики?
Краткий итог
- Пользовательские ошибки помогают выражать бизнес-смысл, а не только технический сбой.
throwприменяй осознанно: когда продолжать выполнение опасно.- Для сложных сценариев создавай свои классы ошибок через
extends Error. - Разделяй обработку ожидаемых (
ValidationError) и неожиданных ошибок. - Четкие типы и сообщения ошибок упрощают UI, логи и отладку всей системы.