Помилки та їх обробка
Обробка помилок у JavaScript будується на трьох рівнях: локальний try/catch/finally навколо ризикованого коду, обробка відхилених промісів в асинхронних операціях і глобальні перехоплювачі як остання лінія оборони. Помилку створюють оператором throw, а для доменних сценаріїв описують власні класи, що успадковують Error.
Теорія
TL;DR
try/catch/finallyловить синхронні винятки;finallyвиконується завжди.- Помилку кидають через
throw new Error('...'), і кидати варто саме об'єктError, а не рядок. - Власні класи (
class ValidationError extends Error) дають типізовані доменні помилки;{ cause }зберігає первинну причину. - В
async/awaitпрацює звичайнийtry/catch; у ланцюжках промісів:.catch(). Promise.allSettledне валить увесь батч через одну невдалу операцію.- Глобальні хендлери:
window.addEventListener('error' | 'unhandledrejection')у браузері,process.on('uncaughtException' | 'unhandledRejection')у Node.js.
Швидкий приклад
try {
risky(); // код, який може впасти
} catch (err) {
console.error(err.message); // обробка або логування
} finally {
cleanup(); // виконується завжди (навіть при помилці)
}Генерація помилки:
if (!user) {
throw new Error('User not found');
}Власні класи помилок
Успадкування від Error дає можливість відрізняти доменні помилки від технічних і обробляти їх вибірково через instanceof.
class ValidationError extends Error {
constructor(message, cause) {
super(message, { cause }); // Node 16+ і сучасні браузери: err.cause
this.name = 'ValidationError';
}
}
throw new ValidationError('Invalid email');Поле cause зберігає первинну помилку, тому низькорівневу причину не втрачено, коли ви загортаєте її у зрозумілу користувачеві.
Асинхронний код
a) async/await разом із try/catch
async function load() {
try {
const res = await fetch('/api/data');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
// обробка, повторна спроба або сповіщення
console.error('Load failed:', err);
return null; // або пробросити далі: throw err
}
}Зверніть увагу: fetch не кидає помилку на HTTP-статус 404 чи 500, тому статус перевіряють вручну через res.ok.
b) Проміси: .catch()
fetch('/api/data')
.then(r => r.json())
.catch(err => console.error('Request failed', err));c) Паралельні операції без падіння всього батча
const results = await Promise.allSettled(urls.map(u => fetch(u)));
for (const r of results) {
if (r.status === 'fulfilled') console.log('OK', r.value);
else console.warn('Failed', r.reason);
}Promise.all відхиляється на першій же невдачі, а Promise.allSettled дочікується всіх і повертає статус кожної операції окремо.
Глобальні перехоплювачі (остання лінія оборони)
Браузер
// синхронні та непіймані винятки
window.addEventListener('error', (event) => {
console.error('Uncaught error:', event.error, event.message, event.filename, event.lineno);
});
// непіймані відхилення промісів
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled rejection:', event.reason);
});Node.js
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception', err);
// логування та коректне завершення процесу
process.exit(1);
});
process.on('unhandledRejection', (reason) => {
console.error('Unhandled Rejection', reason);
// рішення залежить від політики застосунку: логувати і завершувати чи продовжувати
});Глобальні хендлери, це страховка, а не заміна локальної обробки.
Після uncaughtException процес перебуває в невизначеному стані, тому рекомендована практика: залогувати, коректно закрити ресурси і завершити процес, доручивши перезапуск менеджеру процесів.
Типові помилки
- Порожній
catch. Блок, що мовчки ковтає виняток, перетворює баг на невидиму поведінку; щонайменше логуйте. throw 'текст'замістьthrow new Error('текст'). Рядок не має аніstack, аніname, тому стек-трейс втрачено.- Забутий
awaitперед викликом усерединіtry. Проміс відхилиться вже після виходу з блока, іcatchйого не побачить. - Покладатися на те, що
fetchкине помилку на 500. Він відхиляється лише на мережевій помилці, статус перевіряють черезres.ok. try/catchнавколо асинхронного колбека. Виняток усерединіsetTimeout(() => { ... })вилітає в інший такт event loop і зовнішнім блоком не ловиться.- Глобальний хендлер замість локального. Він годиться для телеметрії, але не дає користувачеві осмисленого повідомлення і не відновлює стан застосунку.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.