Ты пишешь REST API для интернет-магазина. Когда что-то идёт не так, нужно не просто бросить ошибку, а сообщить конкретно что произошло: товар не найден? Нет прав доступа? Неверные данные в форме?
Встроенные TypeError и RangeError слишком общие. В реальных проектах создают иерархию собственных классов ошибок — это делает код читаемым, а обработку ошибок — точной.
extends и super() — именно это используется для расширения Errorinstanceof в catch для различения типов ошибокclass ValidationError extends Error {
constructor(message, field) {
super(message) // передаём message в Error
this.name = 'ValidationError' // читаемое имя для логов
this.field = field // дополнительный контекст
}
}
const err = new ValidationError('Email некорректен', 'email')
console.log(err.message) // 'Email некорректен'
console.log(err.name) // 'ValidationError'
console.log(err.field) // 'email'
console.log(err instanceof ValidationError) // true
console.log(err instanceof Error) // true — сохраняется цепочкаВ реальных проектах строят дерево: базовый класс для всего приложения, от него — конкретные типы:
// Базовый класс — все ошибки приложения
class AppError extends Error {
constructor(message, statusCode = 500) {
super(message)
this.name = 'AppError'
this.statusCode = statusCode
}
}
// 404 — ресурс не найден
class NotFoundError extends AppError {
constructor(resource, id) {
super(`${resource} с id=${id} не найден`, 404)
this.name = 'NotFoundError'
}
}
// 400 — неверные данные от клиента
class ValidationError extends AppError {
constructor(message, field) {
super(message, 400)
this.name = 'ValidationError'
this.field = field
}
}
// 403 — нет доступа
class ForbiddenError extends AppError {
constructor(action) {
super(`Действие "${action}" запрещено`, 403)
this.name = 'ForbiddenError'
}
}function getProductById(id, products) {
if (!Number.isInteger(id) || id <= 0) {
throw new ValidationError('id должен быть положительным целым числом', 'id')
}
const product = products.find(p => p.id === id)
if (!product) {
throw new NotFoundError('Product', id)
}
return product
}
// В обработчике запроса:
try {
const product = getProductById(req.params.id, db.products)
res.json(product)
} catch (e) {
if (e instanceof NotFoundError) {
res.status(404).json({ error: e.message })
} else if (e instanceof ValidationError) {
res.status(400).json({ error: e.message, field: e.field })
} else if (e instanceof AppError) {
res.status(e.statusCode).json({ error: e.message })
} else {
throw e // Неожиданная ошибка — пробрасываем дальше
}
}1. Читаемость — NotFoundError говорит сама за себя, TypeError — нет
2. Структурированный контекст — поля field, statusCode несут дополнительные данные
3. Точная обработка — instanceof NotFoundError vs instanceof AppError
4. Документация через код — список классов ошибок = список возможных сбоев API
5. Stack trace — сохраняется как у обычных Error, отладка не страдает
1. Забыли this.name — ошибка показывается как "Error":
// Сломано — имя будет 'Error', а не 'ValidationError':
class ValidationError extends Error {
constructor(message, field) {
super(message)
// забыли this.name = 'ValidationError'
this.field = field
}
}
const e = new ValidationError('Ошибка', 'email')
console.log(e.name) // 'Error' — неожиданно!
// Исправлено:
class ValidationError extends Error {
constructor(message, field) {
super(message)
this.name = 'ValidationError' // обязательно!
this.field = field
}
}2. Ловят AppError вместо конкретного подкласса:
// Сломано — все ошибки обрабатываются одинаково:
catch (e) {
if (e instanceof AppError) {
res.status(500).json({ error: e.message }) // 404 превращается в 500!
}
}
// Исправлено — сначала конкретные, потом общий:
catch (e) {
if (e instanceof NotFoundError) {
res.status(404).json({ error: e.message })
} else if (e instanceof AppError) {
res.status(e.statusCode).json({ error: e.message })
}
}3. Бросают строки вместо Error-объектов:
// Сломано — нет stack trace, нет instanceof:
throw 'Пользователь не найден'
// Исправлено:
throw new NotFoundError('User', userId)instanceofHttpException и его наследники (NotFoundException, BadRequestException)ApolloError, AuthenticationError, ForbiddenErrorUserNotFoundError, PaymentFailedError, DuplicateEmailErrorИерархия ошибок API интернет-магазина с обработкой по типам
// Иерархия ошибок
class AppError extends Error {
constructor(message, statusCode = 500) {
super(message)
this.name = 'AppError'
this.statusCode = statusCode
}
}
class NotFoundError extends AppError {
constructor(resource, id) {
super(`${resource} с id=${id} не найден`, 404)
this.name = 'NotFoundError'
this.resource = resource
}
}
class ValidationError extends AppError {
constructor(message, field) {
super(message, 400)
this.name = 'ValidationError'
this.field = field
}
}
class ForbiddenError extends AppError {
constructor(action) {
super(`Действие "${action}" запрещено`, 403)
this.name = 'ForbiddenError'
}
}
// Данные "базы данных"
const products = [
{ id: 1, name: 'MacBook Pro', price: 180000, adminOnly: false },
{ id: 2, name: 'Серверный GPU', price: 900000, adminOnly: true },
]
// Сервисный слой
function getProduct(id, userRole = 'user') {
if (!id || typeof id !== 'number') {
throw new ValidationError('id должен быть числом', 'id')
}
const product = products.find(p => p.id === id)
if (!product) {
throw new NotFoundError('Product', id)
}
if (product.adminOnly && userRole !== 'admin') {
throw new ForbiddenError('просмотр этого товара')
}
return product
}
// Универсальный обработчик ошибок
function handleRequest(fn) {
try {
const result = fn()
console.log('Успех:', JSON.stringify(result))
} catch (e) {
if (e instanceof ValidationError) {
console.log(`[${e.statusCode}] Валидация (поле "${e.field}"): ${e.message}`)
} else if (e instanceof NotFoundError) {
console.log(`[${e.statusCode}] Не найден: ${e.message}`)
} else if (e instanceof ForbiddenError) {
console.log(`[${e.statusCode}] Доступ запрещён: ${e.message}`)
} else if (e instanceof AppError) {
console.log(`[${e.statusCode}] Ошибка приложения: ${e.message}`)
} else {
throw e
}
}
}
handleRequest(() => getProduct(1, 'user')) // Успех: {...}
handleRequest(() => getProduct(99, 'user')) // [404] Не найден: ...
handleRequest(() => getProduct(2, 'user')) // [403] Доступ запрещён: ...
handleRequest(() => getProduct('abc', 'admin')) // [400] Валидация (поле "id"): ...
// instanceof работает по всей иерархии
const err = new NotFoundError('Order', 42)
console.log(err instanceof NotFoundError) // true
console.log(err instanceof AppError) // true
console.log(err instanceof Error) // trueТы пишешь REST API для интернет-магазина. Когда что-то идёт не так, нужно не просто бросить ошибку, а сообщить конкретно что произошло: товар не найден? Нет прав доступа? Неверные данные в форме?
Встроенные TypeError и RangeError слишком общие. В реальных проектах создают иерархию собственных классов ошибок — это делает код читаемым, а обработку ошибок — точной.
extends и super() — именно это используется для расширения Errorinstanceof в catch для различения типов ошибокclass ValidationError extends Error {
constructor(message, field) {
super(message) // передаём message в Error
this.name = 'ValidationError' // читаемое имя для логов
this.field = field // дополнительный контекст
}
}
const err = new ValidationError('Email некорректен', 'email')
console.log(err.message) // 'Email некорректен'
console.log(err.name) // 'ValidationError'
console.log(err.field) // 'email'
console.log(err instanceof ValidationError) // true
console.log(err instanceof Error) // true — сохраняется цепочкаВ реальных проектах строят дерево: базовый класс для всего приложения, от него — конкретные типы:
// Базовый класс — все ошибки приложения
class AppError extends Error {
constructor(message, statusCode = 500) {
super(message)
this.name = 'AppError'
this.statusCode = statusCode
}
}
// 404 — ресурс не найден
class NotFoundError extends AppError {
constructor(resource, id) {
super(`${resource} с id=${id} не найден`, 404)
this.name = 'NotFoundError'
}
}
// 400 — неверные данные от клиента
class ValidationError extends AppError {
constructor(message, field) {
super(message, 400)
this.name = 'ValidationError'
this.field = field
}
}
// 403 — нет доступа
class ForbiddenError extends AppError {
constructor(action) {
super(`Действие "${action}" запрещено`, 403)
this.name = 'ForbiddenError'
}
}function getProductById(id, products) {
if (!Number.isInteger(id) || id <= 0) {
throw new ValidationError('id должен быть положительным целым числом', 'id')
}
const product = products.find(p => p.id === id)
if (!product) {
throw new NotFoundError('Product', id)
}
return product
}
// В обработчике запроса:
try {
const product = getProductById(req.params.id, db.products)
res.json(product)
} catch (e) {
if (e instanceof NotFoundError) {
res.status(404).json({ error: e.message })
} else if (e instanceof ValidationError) {
res.status(400).json({ error: e.message, field: e.field })
} else if (e instanceof AppError) {
res.status(e.statusCode).json({ error: e.message })
} else {
throw e // Неожиданная ошибка — пробрасываем дальше
}
}1. Читаемость — NotFoundError говорит сама за себя, TypeError — нет
2. Структурированный контекст — поля field, statusCode несут дополнительные данные
3. Точная обработка — instanceof NotFoundError vs instanceof AppError
4. Документация через код — список классов ошибок = список возможных сбоев API
5. Stack trace — сохраняется как у обычных Error, отладка не страдает
1. Забыли this.name — ошибка показывается как "Error":
// Сломано — имя будет 'Error', а не 'ValidationError':
class ValidationError extends Error {
constructor(message, field) {
super(message)
// забыли this.name = 'ValidationError'
this.field = field
}
}
const e = new ValidationError('Ошибка', 'email')
console.log(e.name) // 'Error' — неожиданно!
// Исправлено:
class ValidationError extends Error {
constructor(message, field) {
super(message)
this.name = 'ValidationError' // обязательно!
this.field = field
}
}2. Ловят AppError вместо конкретного подкласса:
// Сломано — все ошибки обрабатываются одинаково:
catch (e) {
if (e instanceof AppError) {
res.status(500).json({ error: e.message }) // 404 превращается в 500!
}
}
// Исправлено — сначала конкретные, потом общий:
catch (e) {
if (e instanceof NotFoundError) {
res.status(404).json({ error: e.message })
} else if (e instanceof AppError) {
res.status(e.statusCode).json({ error: e.message })
}
}3. Бросают строки вместо Error-объектов:
// Сломано — нет stack trace, нет instanceof:
throw 'Пользователь не найден'
// Исправлено:
throw new NotFoundError('User', userId)instanceofHttpException и его наследники (NotFoundException, BadRequestException)ApolloError, AuthenticationError, ForbiddenErrorUserNotFoundError, PaymentFailedError, DuplicateEmailErrorИерархия ошибок API интернет-магазина с обработкой по типам
// Иерархия ошибок
class AppError extends Error {
constructor(message, statusCode = 500) {
super(message)
this.name = 'AppError'
this.statusCode = statusCode
}
}
class NotFoundError extends AppError {
constructor(resource, id) {
super(`${resource} с id=${id} не найден`, 404)
this.name = 'NotFoundError'
this.resource = resource
}
}
class ValidationError extends AppError {
constructor(message, field) {
super(message, 400)
this.name = 'ValidationError'
this.field = field
}
}
class ForbiddenError extends AppError {
constructor(action) {
super(`Действие "${action}" запрещено`, 403)
this.name = 'ForbiddenError'
}
}
// Данные "базы данных"
const products = [
{ id: 1, name: 'MacBook Pro', price: 180000, adminOnly: false },
{ id: 2, name: 'Серверный GPU', price: 900000, adminOnly: true },
]
// Сервисный слой
function getProduct(id, userRole = 'user') {
if (!id || typeof id !== 'number') {
throw new ValidationError('id должен быть числом', 'id')
}
const product = products.find(p => p.id === id)
if (!product) {
throw new NotFoundError('Product', id)
}
if (product.adminOnly && userRole !== 'admin') {
throw new ForbiddenError('просмотр этого товара')
}
return product
}
// Универсальный обработчик ошибок
function handleRequest(fn) {
try {
const result = fn()
console.log('Успех:', JSON.stringify(result))
} catch (e) {
if (e instanceof ValidationError) {
console.log(`[${e.statusCode}] Валидация (поле "${e.field}"): ${e.message}`)
} else if (e instanceof NotFoundError) {
console.log(`[${e.statusCode}] Не найден: ${e.message}`)
} else if (e instanceof ForbiddenError) {
console.log(`[${e.statusCode}] Доступ запрещён: ${e.message}`)
} else if (e instanceof AppError) {
console.log(`[${e.statusCode}] Ошибка приложения: ${e.message}`)
} else {
throw e
}
}
}
handleRequest(() => getProduct(1, 'user')) // Успех: {...}
handleRequest(() => getProduct(99, 'user')) // [404] Не найден: ...
handleRequest(() => getProduct(2, 'user')) // [403] Доступ запрещён: ...
handleRequest(() => getProduct('abc', 'admin')) // [400] Валидация (поле "id"): ...
// instanceof работает по всей иерархии
const err = new NotFoundError('Order', 42)
console.log(err instanceof NotFoundError) // true
console.log(err instanceof AppError) // true
console.log(err instanceof Error) // trueТы разрабатываешь API для системы аутентификации. Создай класс `HttpError extends Error` с полями `statusCode` и стандартным `message`. Создай функцию `authenticate(token, users)`: - Если `token` не строка — `HttpError(400, 'Invalid token format')` - Если token не найден в users — `HttpError(401, 'Unauthorized')` - Если пользователь заблокирован (`blocked: true`) — `HttpError(403, 'Account blocked')` - Иначе возвращает найденного пользователя Обработай все случаи с выводом `statusCode` и `message`.
В конструкторе: super(message), затем this.statusCode = statusCode. Для проверки типа: typeof token !== "string". Для поиска: users.find(u => u.token === token). Если !user — 401, если user.blocked — 403.