Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Яка різниця між async/await i chaining?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**async/await** - синтаксичний цукор над Promise, який дозволяє писати асинхронний код зверху вниз як синхронний. Chaining послідовно з'єднує кроки через `.then()`. ```javascript // Chaining fetch('/api').then(r => r.json()).then(data => console.log(data)).catch(console.error); // Async/await (той самий результат) async function get() { try { const r = await fetch('/api'); console.log(await r.json()); } catch(e) { console.error(e); } } ``` **Ключове:** продуктивність однакова; обидва використовують одну чергу мікротасків Promise.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**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 для паралельних запитів](/questions/promise-all-allsettled-race) ### Таблиця порівняння | Аспект | Async/Await | Chaining (.then()) | |---|---|---| | Читабельність | Лінійна, схожа на sync | Вкладена при глибоких ланцюжках | | Обробка помилок | Один `try/catch` | `.catch()` для кожного кроку | | Продуктивність | Однакова | Однакова | | Підтримка браузерів | ES2017+ (99% глобально) | ES6+ (100% глобально) | | Дебагінг | Стек зупиняється на рядку `await` | Стек показує весь ланцюжок | | Найкраще для | Багатокрокова логіка, React, API | Прості утиліти, одноразові операції | ### Як це компілюється V8 перетворює async-функції на state machine. Кожен `await` передає управління через `Promise.then()`, який відновлює функцію після резолюції промісу. Обидва підходи ставлять мікротаски в одну чергу [event loop](/questions/yak-pratsyuye-event-loop-v-javascript). Різниці в рантаймі немає. ### Типові помилки **Пропущений `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 легше розширюється - наприклад, якщо між кроками потрібно додати валідацію або логування.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.