Яка різниця між 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()для кожного кроку окремо
Швидкий приклад
// 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/Await | Chaining (.then()) |
|---|---|---|
| Читабельність | Лінійна, схожа на sync | Вкладена при глибоких ланцюжках |
| Обробка помилок | Один try/catch | .catch() для кожного кроку |
| Продуктивність | Однакова | Однакова |
| Підтримка браузерів | ES2017+ (99% глобально) | ES6+ (100% глобально) |
| Дебагінг | Стек зупиняється на рядку await | Стек показує весь ланцюжок |
| Найкраще для | Багатокрокова логіка, React, API | Прості утиліти, одноразові операції |
Як це компілюється
V8 перетворює async-функції на state machine. Кожен await передає управління через Promise.then(), який відновлює функцію після резолюції промісу. Обидва підходи ставлять мікротаски в одну чергу event loop. Різниці в рантаймі немає.
Типові помилки
Пропущений await в циклі
// Неправильно - 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-функції
async function noCatch() {
await fetch('/fail'); // UnhandledRejectionWarning в Node.js 15+
}
// Node.js v15+ завершує процес при необроблених відхиленнях.
// Огортай у try/catch або додавай .catch() при виклику.Послідовне очікування там, де можна паралельно
// Повільно - чекає першого fetch перед стартом другого
const d1 = await fetch('/1');
const d2 = await fetch('/2');
// Швидко - обидва стартують одночасно
const [d1, d2] = await Promise.all([fetch('/1'), fetch('/2')]);await поза async-функцією
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-запит, два стилі
// 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
// Три послідовні асинхронні кроки в одному обробнику
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 легше розширюється - наприклад, якщо між кроками потрібно додати валідацію або логування.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.