Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Обробка помилок у async-функціях». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Будь-яка `async`-функція завжди повертає проміс, тому помилка всередині неї, це просто перехід цього промісу у стан `rejected`, і ловити її можна або `try...catch` усередині функції, або `.catch()` зовні при виклику.** `try...catch` перехоплює і явний `throw`, і відхилені проміси, на яких стоїть `await`, бо `await` розгортає rejection у звичайне виключення. Зовнішній `.catch()` спрацьовує вже на результаті виклику, тому зручний, коли рішення про помилку ухвалює викликач. Обидва підходи комбінують: усередині логують і кидають далі новий `Error`, а зовні показують користувачу. Для коду без багатьох вкладених `try` застосовують обгортку, що повертає `[err, data]`, а те, що не спіймали ніде, ловить глобальний обробник `unhandledrejection`. ```javascript async function getData() { try { const res = await fetch('https://api.example.com/missing'); return await res.json(); } catch (error) { console.error('Request failed:', error.message); throw new Error('Data processing failed'); } } ``` **Ключове:** помилка в `async`, це `rejected`-проміс; ловіть її `try...catch` усередині або `.catch()` зовні.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Будь-яка `async`-функція завжди повертає проміс, тому помилка всередині неї переводить цей проміс у стан `rejected`.** Звідси й два основні способи обробки: `try...catch` усередині самої функції та `.catch()` зовні, під час виклику. ## Теорія ### TL;DR - `async`-функція завжди повертає проміс; `throw` усередині неї, це `Promise.reject()`. - `try...catch` усередині ловить і явний `throw`, і відхилені проміси, на яких стоїть `await`. - `.catch()` зовні ловить ту саму помилку, але вже на місці виклику функції. - Ці способи комбінують: усередині логують і кидають нову помилку далі, зовні показують її користувачу. - Патерн `[err, data]` дозволяє обійтися без `try...catch` на кожному кроці. - Усе, що не спіймали, ловить глобальний обробник `unhandledrejection`. ### Швидкий приклад ```javascript async function getData() { try { const res = await fetch('https://api.github.com/404'); // помилка мережі const data = await res.json(); console.log('Отримано:', data); } catch (error) { console.error('Помилка всередині async:', error.message); } } getData(); ``` Вивід: ```text Помилка всередині async: Failed to fetch ``` ### try...catch усередині функції Це найзвичніший і найчитабельніший спосіб: код виглядає так само, як синхронний. `try...catch` ловить: - помилки, викинуті через `throw`; - відхилені проміси, на яких стоїть `await`. Працює це тому, що `await` під капотом розгортає rejection у звичайне виключення: ```javascript const result = await Promise.reject('failure'); // викине виключення, керування перейде в блок catch ``` Тобто відхилений проміс усередині `async`-функції поводиться так само, як звичайний `throw`, і його можна ловити тим самим `try...catch`. ### .catch() зовні та ручний throw Оскільки `async`-функція повертає `Promise`, помилку можна обробити звичайним `.catch()`: ```javascript async function getUser() { const res = await fetch('https://api.example.com/missing'); return res.json(); } getUser() .then(console.log) .catch(err => console.error('Помилка зовні:', err.message)); ``` Вивід: ```text Помилка зовні: Failed to fetch ``` Відмінність від `try...catch` у тому, що тут помилка обробляється під час виклику, а не всередині функції. Так само працює й ручний `throw`: ```javascript async function example() { const data = await Promise.resolve('OK'); if (!data) throw new Error('Немає даних!'); return data; } example() .then(console.log) .catch(err => console.error(err.message)); ``` Вивід: ```text OK ``` Якщо умова спрацює, `throw` перетвориться на `Promise.reject()`, і його впіймає зовнішній `.catch()`. ### Перехоплення з повторним throw і кілька рівнів try...catch Інколи помилку треба обробити частково, наприклад залогувати, і передати далі: ```javascript async function processData() { try { const res = await fetch('https://api.example.com/missing'); return await res.json(); } catch (err) { console.error('Помилка запиту:', err.message); throw new Error('Помилка під час обробки даних'); } } processData().catch(err => console.error('Фінальна помилка:', err.message)); ``` Вивід: ```text Помилка запиту: Failed to fetch Фінальна помилка: Помилка під час обробки даних ``` Якщо різні етапи мають різні сценарії відновлення, використовують кілька `try...catch`: ```javascript async function pipeline() { try { const res = await fetch('/api/data'); try { const data = await res.json(); console.log('Дані:', data); } catch (err) { console.error('Помилка парсингу JSON:', err.message); } } catch (err) { console.error('Помилка запиту:', err.message); } } ``` Такий підхід корисний, коли збій мережі та невалідний JSON треба обробляти окремо. ### Патерн [err, data] без try...catch Інколи зручніше скористатися допоміжною функцією, яка ніколи не відхиляється, а повертає пару: ```javascript const to = (promise) => promise.then(data => [null, data]).catch(err => [err]); async function run() { const [err, data] = await to(fetch('/api/user').then(r => r.json())); if (err) return console.error('Помилка:', err.message); console.log(data); } ``` Цей патерн часто використовують у Node.js, щоб не писати `try...catch` на кожному кроці й тримати обробку помилок пласкою. ### Глобальний перехоплювач unhandledrejection Якщо `.catch()` або `try...catch` забули, помилку ще можна перехопити глобально: ```javascript window.addEventListener('unhandledrejection', (event) => { console.error('Необроблений проміс:', event.reason); }); ``` Це аналог `try...catch` для всіх промісів, у яких немає власного `.catch()`. У Node.js той самий сенс має `process.on('unhandledRejection', handler)`. Такий обробник, це остання лінія оборони й місце для логування, а не заміна нормальної обробки. | Спосіб | Де ловить помилку | Приклад | | --- | --- | --- | | `try...catch` | усередині `async`-функції | `try { await ... } catch (e) { ... }` | | `.catch()` | під час виклику функції | `myAsync().catch(e => ...)` | | Глобальний перехоплювач | для всіх необроблених | `window.addEventListener('unhandledrejection', ...)` | | Патерн `[err, data]` | через обгортку | `const [err, res] = await to(promise)` | > Проста аналогія: `async`-функція, це квест. `await`, це завдання, яке може провалитися; `try...catch`, це страховка, що дає змогу вирішити, як діяти далі; `.catch()` зовні, це гейм-майстер, який ловить фатальні помилки, коли гравець не впорався. ### Типові помилки - Забувати `await` перед `res.json()` у `return`: у конструкції `try { return res.json(); }` помилка парсингу вилетить уже поза `try`, і локальний `catch` її не побачить. - Ловити помилку, нічого не робити й не кидати її далі: виклик отримає `undefined` і поводитиметься так, ніби все пройшло успішно. - Думати, що `fetch` кидає виключення на HTTP 404 або 500. Він відхиляє проміс лише на мережевій помилці, статус треба перевіряти вручну через `res.ok`. - Ставити `try...catch` навколо виклику асинхронної функції без `await`: без `await` виключення не потрапить у цей блок, а стане необробленим rejection. - Покладатися лише на глобальний `unhandledrejection`: він спрацьовує запізно й не дає користувачу зрозумілого повідомлення. - Кидати далі рядок замість `Error`: губиться стек викликів, і `err.message` буде `undefined`.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.