Skip to main content

Помилки та їх обробка

Обробка помилок у 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.

Швидкий приклад

javascript
try { risky(); // код, який може впасти } catch (err) { console.error(err.message); // обробка або логування } finally { cleanup(); // виконується завжди (навіть при помилці) }

Генерація помилки:

javascript
if (!user) { throw new Error('User not found'); }

Власні класи помилок

Успадкування від Error дає можливість відрізняти доменні помилки від технічних і обробляти їх вибірково через instanceof.

javascript
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

javascript
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()

javascript
fetch('/api/data') .then(r => r.json()) .catch(err => console.error('Request failed', err));

c) Паралельні операції без падіння всього батча

javascript
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 дочікується всіх і повертає статус кожної операції окремо.

Глобальні перехоплювачі (остання лінія оборони)

Браузер

javascript
// синхронні та непіймані винятки 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

javascript
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 і зовнішнім блоком не ловиться.
  • Глобальний хендлер замість локального. Він годиться для телеметрії, але не дає користувачеві осмисленого повідомлення і не відновлює стан застосунку.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.