Пользовательские ошибки
Пользовательские ошибки — классы, расширяющие встроенный
Error, которые позволяют создавать семантически значимые типы ошибок для конкретной предметной области приложения.
Зачем нужно
Стандартный Error с произвольным message неудобен для программной обработки: приходится парсить строку, чтобы понять причину сбоя. Пользовательские классы ошибок позволяют точно проверить тип (instanceof ValidationError), добавить структурированные данные и выстроить иерархию ошибок.
Где используется
- Валидация данных форм и API
- Бизнес-логика (недостаточно прав, лимит превышен)
- HTTP-ошибки в серверных приложениях (Node.js, Express)
- Слой доступа к данным (NotFoundError, DatabaseError)
Базовый пользовательский класс ошибки
class AppError extends Error {
constructor(message, code) {
super(message); // устанавливает this.message
this.name = 'AppError'; // имя типа ошибки
this.code = code;
// Восстанавливаем стек вызовов (важно в V8)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}
const err = new AppError('Что-то пошло не так', 'INTERNAL');
console.log(err.name); // AppError
console.log(err.message); // Что-то пошло не так
console.log(err.code); // INTERNAL
console.log(err instanceof AppError); // true
console.log(err instanceof Error); // true
Иерархия ошибок
// Базовый класс домена
class DomainError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name; // автоматически из имени класса
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}
// Конкретные типы
class ValidationError extends DomainError {
constructor(field, message) {
super(message);
this.field = field;
}
}
class NotFoundError extends DomainError {
constructor(resource, id) {
super(`${resource} с id=${id} не найден`);
this.resource = resource;
this.id = id;
}
}
class AuthError extends DomainError {
constructor(message = 'Нет доступа') {
super(message);
this.statusCode = 403;
}
}
Использование в приложении
function validateAge(age) {
if (typeof age !== 'number') {
throw new ValidationError('age', 'Возраст должен быть числом');
}
if (age < 0 || age > 150) {
throw new ValidationError('age', 'Возраст вне допустимого диапазона');
}
}
async function getUser(id) {
const user = await db.findById(id);
if (!user) throw new NotFoundError('User', id);
return user;
}
// Обработка с instanceof
try {
validateAge('не число');
} catch (err) {
if (err instanceof ValidationError) {
console.error(`Поле '${err.field}': ${err.message}`);
// Показать пользователю
} else if (err instanceof NotFoundError) {
console.error(`Ресурс '${err.resource}' не найден`);
} else {
throw err; // непредвиденная ошибка — пробрасываем дальше
}
}
HTTP-ошибки (Node.js / Express)
class HttpError extends Error {
constructor(statusCode, message) {
super(message);
this.name = 'HttpError';
this.statusCode = statusCode;
}
}
class BadRequestError extends HttpError {
constructor(msg = 'Bad Request') { super(400, msg); }
}
class UnauthorizedError extends HttpError {
constructor(msg = 'Unauthorized') { super(401, msg); }
}
class ForbiddenError extends HttpError {
constructor(msg = 'Forbidden') { super(403, msg); }
}
// Middleware Express
app.use((err, req, res, next) => {
if (err instanceof HttpError) {
return res.status(err.statusCode).json({ error: err.message });
}
res.status(500).json({ error: 'Internal Server Error' });
});
Частые ошибки
1. Не установлено this.name
class MyError extends Error {
constructor(msg) {
super(msg);
// Без this.name = 'MyError'
// err.name будет 'Error', не 'MyError'
}
}
2. Потеря стека при пробрасывании
// Плохо: исходный стек теряется
catch (err) { throw new Error(err.message); }
// Хорошо: сохраняем причину
catch (err) { throw new AppError('Ошибка', 'CODE', { cause: err }); }
Связанные темы
- Типы ошибок -- Error, TypeError, ReferenceError
- Обработка ошибок в async коде
- Оператор instanceof
- _MOC JavaScript