Skip to main content

Яка різниця між async/await i chaining?

async/await - синтаксичний цукор над Promise, який дозволяє писати асинхронний код зверху вниз як синхронний. Chaining послідовно з'єднує кроки через виклики .then() на тому самому Promise.

Теорія

TL;DR

  • async/await читається як синхронний код; chaining - як конвеєр
  • Аналогія: chaining - це естафета (кожен бігун передає паличку), async/await - один бігун, який зупиняється на контрольних точках
  • Обидва компілюються в однакові Promise під капотом, тому продуктивність однакова
  • async/await для 2+ послідовних кроків; chaining для простих одноразових операцій
  • Обробка помилок: async/await накриває все одним try/catch; chaining дозволяє .catch() для кожного кроку окремо

Швидкий приклад

javascript
// Chaining fetch('https://jsonplaceholder.typicode.com/users/1') .then(res => res.json()) .then(data => console.log(data.name)) // "Leanne Graham" .catch(err => console.error(err)); // Async/await - той самий результат, лінійний потік async function getUser() { try { const res = await fetch('https://jsonplaceholder.typicode.com/users/1'); const data = await res.json(); console.log(data.name); // "Leanne Graham" } catch (err) { console.error(err); } } getUser();

Результат однаковий. Різниця суто в тому, як читається код.

Головна різниця

Async/await візуально розгортає ланцюжок промісів у лінійний код, але під час виконання V8 компілює це в state machine на тих самих мікротасках Promise. Практична різниця - в читабельності та стилі обробки помилок. try/catch в async/await накриває всі помилки функції одним блоком. .catch() в chaining можна прикріпити до кожного конкретного кроку, якщо потрібен дрібний контроль.

Коли що використовувати

  • 2+ послідовні асинхронні кроки: async/await (залишається читабельним на будь-якій глибині)
  • Проста одна операція: chaining (не потрібна async-обгортка)
  • Цикли або умови: async/await (for...of з await працює природно)
  • Різна обробка помилок для кожного кроку: chaining (окремий .catch())
  • Паралельні операції: жоден сам по собі, потрібен Promise.all для паралельних запитів

Таблиця порівняння

АспектAsync/AwaitChaining (.then())
ЧитабельністьЛінійна, схожа на syncВкладена при глибоких ланцюжках
Обробка помилокОдин try/catch.catch() для кожного кроку
ПродуктивністьОднаковаОднакова
Підтримка браузерівES2017+ (99% глобально)ES6+ (100% глобально)
ДебагінгСтек зупиняється на рядку awaitСтек показує весь ланцюжок
Найкраще дляБагатокрокова логіка, React, APIПрості утиліти, одноразові операції

Як це компілюється

V8 перетворює async-функції на state machine. Кожен await передає управління через Promise.then(), який відновлює функцію після резолюції промісу. Обидва підходи ставлять мікротаски в одну чергу event loop. Різниці в рантаймі немає.

Типові помилки

Пропущений await в циклі

javascript
// Неправильно - race condition, непередбачуваний порядок async function badLoop() { const promises = [fetch('/1'), fetch('/2')]; for (const p of promises) { const data = p.json(); // Пропущено await! console.log(await data); } } // Правильно - послідовно і передбачувано async function goodLoop() { const urls = ['/1', '/2']; for (const url of urls) { const res = await fetch(url); const data = await res.json(); console.log(data); // спочатку item 1, потім item 2 } }

Немає try/catch в async-функції

javascript
async function noCatch() { await fetch('/fail'); // UnhandledRejectionWarning в Node.js 15+ } // Node.js v15+ завершує процес при необроблених відхиленнях. // Огортай у try/catch або додавай .catch() при виклику.

Послідовне очікування там, де можна паралельно

javascript
// Повільно - чекає першого fetch перед стартом другого const d1 = await fetch('/1'); const d2 = await fetch('/2'); // Швидко - обидва стартують одночасно const [d1, d2] = await Promise.all([fetch('/1'), fetch('/2')]);

await поза async-функцією

javascript
function notAsync() { const data = await fetch('/data'); // SyntaxError } // Виправлення: додай async до оголошення функції.

Де зустрічається в реальному коді

У більшості Express-проектів, які я бачив, команди повністю переходять на async/await як тільки в одному обробнику з'являється більше двох послідовних звернень до бази. Chaining там просто погано читається.

  • React: async IIFE всередині useEffect для завантаження даних
  • Express: обробники маршрутів async (req, res) => {}, стандарт з Node 7.6
  • Next.js: серверні компоненти та API routes за замовчуванням async/await
  • Axios: повертає Promise, працює з обома стилями
  • Puppeteer: await page.goto() та await page.click() скрізь

Питання на співбесіді

Q: Що трапиться, якщо написати await 42 (не проміс)?
A: Повернеться 42 миттєво. Не-thenable значення автоматично огортаються в resolved Promise. Ніякої паузи не буде.

Q: Чи можна використовувати async/await в консолі браузера?
A: Так, але потрібен IIFE: (async () => { const res = await fetch(...); })(). Top-level await (очікування (await) на верхньому рівні) працює лише в ES2022 модулях.

Q: Як запустити два async-запити паралельно через async/await?
A: Спочатку запускай обидва Promise, потім чекай разом через Promise.all([p1, p2]). Якщо чекати їх по черзі, виконання стане послідовним.

Q: Що відбувається з необробленими відхиленнями в Node.js v15+?
A: Процес завершується. У старших версіях було лише попередження. Завжди обробляй помилки через try/catch або .catch().

Q: Поясни, чому async/await не покращує продуктивність порівняно з chaining.
A: Обидва ставлять мікротаски в одну чергу event loop. V8 компілює async-функції в ланцюжки промісів внутрішньо, тому модель виконання ідентична. Різниця лише в синтаксисі.

Приклади

Базовий: один і той самий API-запит, два стилі

javascript
// Chaining - конвеєрний стиль fetch('https://jsonplaceholder.typicode.com/posts/1') .then(res => res.json()) .then(post => console.log(post.title)) // "sunt aut facere..." .catch(err => console.error(err)); // Async/await - послідовний стиль async function getPost() { try { const res = await fetch('https://jsonplaceholder.typicode.com/posts/1'); const post = await res.json(); console.log(post.title); // "sunt aut facere..." } catch (err) { console.error(err); } } getPost();

Обидва виводять однаковий заголовок поста. З ростом кількості кроків chaining вкладається глибше, async/await залишається плоским.

Середній рівень: обробник маршруту Express

javascript
// Три послідовні асинхронні кроки в одному обробнику app.get('/profile/:id', async (req, res) => { try { const response = await fetch(`https://api.github.com/users/${req.params.id}`); const userData = await response.json(); await db.saveProfile(userData); // зберегти в базу даних res.json(userData); } catch (err) { res.status(500).json({ error: err.message }); } });

З chaining тут було б три вкладених .then() і .catch() в кінці. Версія з async/await легше розширюється - наприклад, якщо між кроками потрібно додати валідацію або логування.

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

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

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

Дочитали статтю?