Найчастіша проблема з помилками - не відсутність обробки, а обробка не в тому місці: try...catch у кожній функції, де помилку логують і повертають null, а вищий рівень так і не дізнається, що щось пішло не так.
Принцип: ловити там, де знаєш, що робити.
Функція, яка не може осмислено відреагувати на помилку, має пропустити її далі. «Що робити» майже завжди вирішує рівень, близький до користувача.
Шари типового застосунку:
1. Низький рівень (HTTP-клієнт, парсери) - перетворює помилки на зрозумілі типи, але не ховає їх:
async function apiRequest(url, options) {
const response = await fetch(url, options);
if (response.status === 422) throw new ValidationError((await response.json()).errors);
if (response.status === 401) throw new UnauthenticatedError();
if (!response.ok) throw new HttpError(response);
return response.json();
}
2. Сервіси й бізнес-логіка - зазвичай не ловлять нічого. Винятки - очікувані ситуації з альтернативною поведінкою: кеш недоступний - читаємо напряму; необов'язкова рекомендація не завантажилася - показуємо сторінку без неї.
3. Межа взаємодії (обробник форми, дія користувача) - тут вирішують, що показати:
async function onSubmit() {
try {
await saveOrder(form);
showToast('Замовлення збережено');
} catch (error) {
if (error instanceof ValidationError) return showFieldErrors(error.errors);
if (error instanceof UnauthenticatedError) return redirectToLogin();
report(error); // неочікуване - у моніторинг
showToast('Не вдалося зберегти. Спробуйте ще раз');
}
}
4. Глобальний рубіж - window.onerror, unhandledrejection, errorHandler фреймворку, error boundary: для того, що проскочило, - звіт у моніторинг і запасний інтерфейс замість білого екрана.
Очікувані й неочікувані помилки:
- очікувані (валідація, 404 «товар не знайдено», немає прав) - частина звичайного сценарію. Їх обробляють і не надсилають у моніторинг як помилки;
- неочікувані (
TypeError, 500) - баги. Їх показують користувачеві загальним повідомленням і обов'язково звітують.
Змішування призводить до того, що моніторинг завалений тисячами «помилок валідації», а справжній баг тоне в шумі.
Антипатерни:
catch (e) { console.log(e) }без подальшої дії - помилка «оброблена», але застосунок у зламаному стані;- повернення
null/falseзамість винятку - виклики далі мусять перевіряти результат, і рано чи пізно перевірку забудуть; - однакове «Щось пішло не так» для всього - і для помилки валідації, і для недоступного сервера;
catchлише для того, щобthrowту саму помилку - шум.
Альтернатива винятками - тип-результат ({ ok: true, value } / { ok: false, error }), популярний у TypeScript для очікуваних помилок: компілятор змушує обробити обидва варіанти. Але для неочікуваних помилок винятки лишаються природнішими.