try...catch ловить лише помилки, що виникають під час виконання блоку try - поки цей код на стеку викликів.
try {
setTimeout(() => {
null.x; // помилка тут
}, 0);
} catch (error) {
console.log('спіймано'); // НЕ виконається
}
Чому: setTimeout лише реєструє колбек і одразу повертає керування. Блок try завершується без помилок. Колбек виконується пізніше, з власного стеку, коли цикл подій дійде до таймера. Зовнішнього try на цьому стеку вже немає. Помилка стає необробленою й іде в глобальний обробник window.onerror.
Те саме для:
- обробників подій (
addEventListener); - колбеків старих асинхронних API;
then-колбеків Promise, якщо не підключеноcatch.
Як правильно:
1. try всередині колбека:
setTimeout(() => {
try {
riskyWork();
} catch (error) {
report(error);
}
}, 0);
2. Перетворити на Promise і використовувати await:
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
try {
await sleep(100);
riskyWork(); // тепер у тій самій async-функції - try спрацює
} catch (error) {
report(error);
}
await «зшиває» асинхронні кроки в одну логічну послідовність: після очікування виконання продовжується всередині того самого try.
3. Але await має бути:
try {
saveDraft(); // async-функція без await
} catch (error) { // не спіймає відхилення
}
try {
await saveDraft(); // спіймає
} catch (error) {
}
4. return з try в async-функції: return fetchData() повертає Promise без очікування - відхилення пройде повз catch цієї функції. return await fetchData() в try - спіймає.
Обробники подій - помилку краще ловити в самому обробнику або загорнути обробники обгорткою:
const safe = (handler) => async (event) => {
try {
await handler(event);
} catch (error) {
report(error);
showToast('Щось пішло не так');
}
};
button.addEventListener('click', safe(onSave));
Загальне правило: помилку можна спіймати лише там, де її очікують. В асинхронному коді очікування - це await чи catch на відповідному Promise.
Докладніше в документації: Керування потоком і обробка помилок