Перейти к содержимому

Error Handling

В одной фразе: правильная обработка ошибок — иерархия кастомных классов с именованными кодами, Error.cause для chaining (ES2022), и явная стратегия разделения операционных ошибок от программных. На собесе проверяют как не “потерять” ошибку в async-коде и как правильно типизировать.

// Иерархия кастомных ошибок — стандарт production-кода
class AppError extends Error {
constructor(message, options = {}) {
super(message, { cause: options.cause }); // ES2022: Error.cause
this.name = this.constructor.name; // 'AppError', не 'Error'
this.code = options.code ?? 'UNKNOWN';
}
}
class NetworkError extends AppError {
constructor(message, { statusCode, ...opts } = {}) {
super(message, opts);
this.statusCode = statusCode;
}
}
class ValidationError extends AppError {
constructor(message, { field, ...opts } = {}) {
super(message, opts);
this.field = field;
}
}
// Error chaining: сохраняем первопричину через Error.cause
async function fetchUser(id) {
try {
const resp = await fetch(`/api/users/${id}`);
if (!resp.ok) throw new NetworkError('HTTP error', { statusCode: resp.status });
return await resp.json();
} catch (e) {
if (e instanceof NetworkError) throw e; // пробрасываем как есть
// Оборачиваем, не теряя оригинал:
throw new AppError('Failed to fetch user', { cause: e, code: 'FETCH_FAILED' });
}
}
// Раскрутка chain:
try { await fetchUser(1); } catch (e) {
console.log(e.message); // 'Failed to fetch user'
console.log(e.cause); // оригинальная ошибка с полным стектрейсом
}
// Async: unhandledRejection — тихая гибель промиса
// ❌ Потерянная ошибка
async function bad() {
const p = riskyOp(); // запустили, не await — ошибка → unhandledRejection
return 'done';
}
// ✅ Явная обработка
const p = riskyOp().catch(err => logger.error(err));
// Глобальный перехват (Node.js)
process.on('unhandledRejection', (reason) => {
logger.fatal(reason);
process.exit(1); // рекомендуется: лучше упасть явно
});
// В catch всегда проверяй тип и re-throw неизвестное:
try { /* ... */ } catch (e) {
if (e instanceof ValidationError) return handleValidation(e);
throw e; // неизвестные ошибки — не глотать!
}

Итог: Иерархия кастомных ошибок с name, code и cause делает debugging и мониторинг на порядок эффективнее. Никогда не “глотай” ошибки в пустом catch.