Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Помилки в async-функціях». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Будь-яка помилка всередині `async`-функції автоматично перетворюється на відхилений проміс, тому ловлять її або блоком `try...catch` усередині функції, або методом `.catch()` на проміс, який функція повертає.** `await` на відхиленому промісі кидає виняток, тож звичайний `try...catch` працює з асинхронним кодом так само, як із синхронним. ```javascript async function getData() { try { const res = await fetch("https://api.example.com/data"); return await res.json(); } catch (error) { console.error("Сталася помилка:", error.message); } } ``` **Ключове:** `try...catch` це обробка всередині функції, `.catch()` це обробка на місці виклику; без жодного з них ви отримаєте необроблене відхилення проміса.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Помилки всередині `async`-функцій автоматично перетворюються на відхилений проміс (`Promise.reject`).** Тому для їх обробки використовують або конструкцію `try...catch` усередині функції, або метод `.catch()` на промісі, який ця функція повертає. ## Теорія ### TL;DR - `async`-функція завжди повертає проміс, а `throw` усередині неї стає `reject`. - `await` на відхиленому промісі кидає виняток, тож його ловить звичайний `try...catch`. - `.catch()` на виклику функції це друга рівноправна опція, зручна на межі застосунку. - Один `try` може покривати кілька `await`, або кожен `await` дістає власний `try` для точкової реакції. - `return` із блоку `catch` «лікує» ситуацію: проміс функції завершиться успішно зі значенням за замовчуванням. - У `Promise.all` падіння будь-якого проміса валить весь виклик, тож для часткових результатів беруть `Promise.allSettled`. ### Швидкий приклад ```javascript async function getData() { try { const res = await fetch("https://no-such-domain.example/data"); const data = await res.json(); console.log(data); } catch (error) { console.error("Сталася помилка:", error.message); } } getData(); // Вивід: Сталася помилка: fetch failed ``` Тут `try...catch` працює так само, як у синхронному коді, але ловить помилки з усіх `await` усередині блоку. ### Чому це працює `await` «зупиняє» виконання функції, поки проміс не завершиться. Якщо проміс відхилено, `await` генерує виняток, і управління передається в `catch`. Код після невдалого `await` не виконується взагалі. ```javascript async function example() { const result = await Promise.reject("Помилка!"); console.log(result); // не виконається } example().catch(console.error); // Вивід: Помилка! ``` Це і є вся магія `async/await`: проміс, що відхилився, перетворюється на звичайний виняток, а звичайний виняток перетворюється назад на відхилений проміс на межі функції. ### Спільна зона try і локальний контроль Кілька `await` можна обгорнути в один `try`, якщо всі помилки обробляються однаково: ```javascript async function loadUserData() { try { const user = await fetch("/api/user"); const posts = await fetch("/api/posts"); const comments = await fetch("/api/comments"); } catch (e) { console.error("Помилка під час завантаження даних:", e); } } ``` Якщо будь-яка з операцій впаде, управління одразу перейде в `catch`, а решта запитів навіть не стартують. Іноді краще обгортати окремі `await`, коли реакції мають бути різними: ```javascript async function example() { try { const user = await fetchUser(); } catch (e) { console.error("Помилка під час завантаження користувача:", e); } try { const posts = await fetchPosts(); } catch (e) { console.error("Помилка під час завантаження постів:", e); } } ``` ### Без try...catch проміс просто відхиляється Якщо не обгортати `await` у `try...catch`, функція просто поверне відхилений проміс, який обробляють через `.catch()` на місці виклику: ```javascript async function fail() { throw new Error("Щось пішло не так"); } fail().catch(err => console.error("Спіймали:", err.message)); // Вивід: Спіймали: Щось пішло не так ``` Це повністю еквівалентно обробці `.catch()` у звичайних промісах. Той самий `.catch()` можна навісити прямо на окремий `await`, коли треба просто «придушити» помилку і піти далі з безпечним значенням: ```javascript async function getUser() { const res = await fetch("/api/user").catch(err => { console.error("Помилка запиту:", err); return null; // повертаємо безпечне значення }); console.log("Результат:", res); } getUser(); ``` Якщо ж у блоці `catch` повернути значення, функція завершиться успішно, і воно потрапить у `.then()`: ```javascript async function example() { try { await Promise.reject("Помилка!"); } catch (e) { console.log("Спіймали:", e); return "Значення за замовчуванням"; } } example().then(result => console.log("Результат:", result)); // Вивід: // Спіймали: Помилка! // Результат: Значення за замовчуванням ``` ### Помилки в паралельних await Якщо ви виконуєте кілька операцій паралельно через `Promise.all` і хоча б один проміс відхилиться, весь `Promise.all` впаде з першою ж помилкою: ```javascript async function parallel() { try { const [a, b] = await Promise.all([ Promise.resolve("ok"), Promise.reject("помилка"), ]); } catch (e) { console.log("Спіймали:", e); } } parallel(); // Вивід: Спіймали: помилка ``` Якщо потрібні результати всіх операцій незалежно від того, які з них упали, беріть `Promise.allSettled`: він завжди виконується успішно і повертає масив об'єктів зі `status` рівним `"fulfilled"` або `"rejected"`. ### Найкращі практики | Ситуація | Що використати | | --- | --- | | Обробка помилок в одній функції | `try...catch` | | Обробка помилок при виклику async-функції | `.catch()` | | Різні типи помилок з різними реакціями | Кілька окремих `try...catch` | | Кілька паралельних `await` | `Promise.allSettled()` замість `Promise.all` | | Логування і проброс далі | `catch (e) { log(e); throw e; }` | ### Типові помилки - **Забути `await` перед викликом.** Функція поверне проміс, а не значення, і помилка не потрапить у `try...catch`, який уже завершився. - **`return promise` замість `return await promise` у блоці `try`.** Без `await` відхилення станеться вже після виходу з `try`, тому локальний `catch` його не побачить. - **Викликати async-функцію без обробки взагалі.** Це дає `unhandledrejection` у браузері й аварійне завершення процесу в Node.js 15 і новіших. - **Вважати, що `fetch` кидає помилку на 404 чи 500.** `fetch` відхиляє проміс лише на мережевому збої, тому статус треба перевіряти вручну через `res.ok`. - **Ловити помилку і мовчки її ковтати.** Порожній `catch` перетворює збій на успіх з `undefined`; або логуйте й робіть `throw e`, або повертайте явне значення за замовчуванням. - **Послідовні `await` там, де потрібні паралельні.** Три незалежні запити в трьох `await` виконаються один за одним; запускайте їх через `Promise.all` або `Promise.allSettled`. - **`try...catch` навколо колбека `setTimeout`.** Виняток із колбека таймера летить у глобальний обробник, а не у ваш `catch`; асинхронну операцію треба обгорнути в проміс.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.