Skip to main content

Помилки в async-функціях

Помилки всередині 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
Кілька паралельних awaitPromise.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; асинхронну операцію треба обгорнути в проміс.

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

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

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