Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке async/await у JavaScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**async/await** - це синтаксис для роботи з Promise без ланцюжків `.then()`, який робить асинхронний код послідовним і читабельним. ```javascript async function getUser(id) { const res = await fetch(`/api/users/${id}`); return await res.json(); } ``` **Ключове:** `async` функції завжди повертають Promise. `await` зупиняє тільки поточну функцію - головний потік продовжує виконання.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**async/await** - це синтаксичний цукор над Promise, доданий у ES2017, який дозволяє писати асинхронний JavaScript-код рядок за рядком, наче він синхронний. ## Теорія ### TL;DR - `async` робить функцію асинхронною і автоматично загортає повернуте значення в Promise - `await` призупиняє тільки ту `async` функцію, де стоїть, не блокуючи головний потік - Аналогія: замовляєш каву в кафе - відходиш убік (await зупиняє функцію), черга рухається далі (event loop виконує інший код), забираєш коли готово (Promise виконується) - Використовуй async/await для 2+ послідовних асинхронних операцій; для одиночних - звичайний Promise - Обробка помилок: `try/catch` замість ланцюжків `.catch()` ### Короткий приклад ```javascript // Promise-ланцюжок - читається зсередини назовні fetch('/api/user') .then(res => res.json()) .then(user => console.log(user.name)) .catch(err => console.error(err)); // async/await - читається зверху вниз async function getUser() { try { const res = await fetch('/api/user'); const user = await res.json(); console.log(user.name); // "Alice" } catch (err) { console.error(err); } } ``` Обидва варіанти роблять одне й те саме. Але async/await читається в тому порядку, в якому все відбувається. ### Головна різниця `await` перетворює Promise у значення, яке можна одразу присвоїти змінній, зупиняючи `async` функцію на цьому рядку до завершення Promise. [Event loop](/questions/event-loop-in-javascript) при цьому продовжує працювати: інший код, таймери та обробники подій нікуди не зникають. Коли Promise виконується, продовження функції стає в чергу мікрозавдань (microtask queue), і виконання поновлюється з того місця, де зупинилось. ### Коли використовувати - Послідовні API-запити (отримати юзера, потім його пости): async/await читається природно - Обробка помилок у кількох кроках: один `try/catch` покриває всі `await` в блоці - Паралельні операції: `Promise.all()` всередині `async` функції - Один простий запит: звичайний [Promise](/questions/what-is-promise-in-javascript) з `.then()` цілком підходить - Легасі-код або бібліотеки без підтримки Promise: колбеки або Promises безпосередньо ### Як це працює всередині V8 компілює `async` функції у стейт-машини на основі генераторів. Кожен `await` передає управління event loop через `Generator.prototype.next()`. Коли очікуваний Promise виконується, продовження функції стає в чергу мікрозавдань, яка обробляється до макрозавдань типу `setTimeout`. Тобто код після `await` завжди виконується раніше ніж колбеки від таймерів. ### Типові помилки **Забули `await` перед Promise** ```javascript async function bad() { const data = fetch('/api/user'); // Пропущено await! console.log(data); // Promise { <pending> } } ``` Функція продовжує виконання одразу. `data` містить об'єкт Promise, а не відповідь сервера. Рішення: додати `await` перед `fetch`. **`throw` без обробки поверненого Promise** ```javascript async function risky() { throw new Error('boom'); // Стає відхиленим Promise } risky(); // Unhandled rejection - падає Node.js ``` Помилка не піднімається синхронно. Вона відхиляє Promise, який повертає `risky()`. Потрібно або додати `.catch()` на виклик, або викликати в `async` функції з `try/catch`. **`await` поза `async` функцією** ```javascript function wrong() { const data = await fetch('/api'); // SyntaxError } ``` `await` працює тільки всередині `async` функцій (або на верхньому рівні ES-модулів). Рішення: позначити функцію як `async`. **Думати, що `await` блокує всю програму** ```javascript async function a() { await delay(1000); console.log('A'); } async function b() { console.log('B'); } a(); b(); // Виведе: "B", потім "A" ``` `await` зупиняє тільки свою `async` функцію. Всі інші продовжують виконуватись. Це найпоширеніша помилка на співбесідах, коли питають про порядок виконання. **Послідовні await там, де можна паралельно** ```javascript // Повільно: кожен запит чекає завершення попереднього const user = await fetchUser(id); const posts = await fetchPosts(id); // Швидко: обидва стартують одночасно const [user, posts] = await Promise.all([fetchUser(id), fetchPosts(id)]); ``` Якщо дві операції не залежать одна від одної, немає причини запускати їх послідовно. Це найчастіша помилка, яку я бачу на code review, і виправляється в один рядок. ### Де зустрічається в реальному коді - React/Next.js: `const data = await fetchUser(id)` у Server Components або `getServerSideProps` - Express: `const user = await db.query('SELECT * FROM users WHERE id = ?', [id])` в обробниках маршрутів - Node.js: `const file = await fs.readFile('data.txt', 'utf8')` через модуль `fs/promises` - Axios: `const res = await axios.get('/api/posts')` у Vue або Nuxt - Puppeteer: `await page.goto(url)` в скриптах автоматизації браузера ### Питання на співбесіді **Q:** Що повертає `async` функція, якщо написати `return 42`? **A:** Завжди Promise. Рушій загортає значення через `Promise.resolve(42)`. Саме число `42` отримаєш тільки після `await` або через `.then()`. **Q:** Чи можна `await` не Promise-значення, наприклад число? **A:** Так. `await 42` одразу повертає `42`. Технічно рушій загортає це в `Promise.resolve()`. Працює, але рідко має сенс поза загальними утилітами. **Q:** Як `await` обробляє відхилення Promise? **A:** Кидає причину відхилення всередину `async` функції як звичайну помилку. Навколишній `try/catch` її перехопить. **Q:** Як скасувати операцію, яку вже очікуємо через `await`? **A:** Самі Promise не можна скасувати, але `fetch` приймає сигнал від `AbortController`. Передай `{ signal: ac.signal }` у запит і виклич `ac.abort()` коли потрібно. **Q:** Яка різниця між мікрозавданнями і макрозавданнями у контексті `await`? (рівень senior) **A:** Продовження після `await` стає в чергу мікрозавдань (microtask queue). Мікрозавдання виконуються після поточного синхронного коду, але до макрозавдань типу `setTimeout`. Тобто код після `await somePromise` виконається раніше ніж будь-який `setTimeout(fn, 0)`. Саме на цьому порядку базується планувальник React Concurrent mode. ## Приклади ### Базовий: що повертає async функція ```javascript async function add(a, b) { return a + b; // Автоматично загортається в Promise.resolve() } // Щоб отримати значення - потрібен .then() або await add(2, 3).then(result => console.log(result)); // 5 ``` `async` загортає будь-яке повернуте значення в Promise. Навіть просту арифметику. Число отримаєш тільки через Promise. ### Середній: обробник входу в Express ```javascript app.post('/login', async (req, res) => { try { const { email, password } = req.body; const user = await User.findOne({ email }); // Запит до БД if (!user || !await bcrypt.compare(password, user.hash)) { return res.status(401).json({ error: 'Invalid credentials' }); } const token = jwt.sign({ id: user.id }, process.env.SECRET); res.json({ token }); } catch (err) { res.status(500).json({ error: 'Server error' }); } }); // Успіх: { token: "eyJ..." } // Помилка автентифікації: 401 { error: "Invalid credentials" } ``` Дві послідовні асинхронні операції - запит до БД і перевірка пароля - кожен рядок чекає попереднього. Один `try/catch` покриває обидві. Саме так виглядає async/await у реальному Node.js-коді. ### Просунутий: гонки стану у паралельних операціях ```javascript // Виглядає безпечно, але не є: async function tricky() { let x = 0; const p1 = (async () => { await null; x = 1; })(); const p2 = (async () => { await null; x = 2; })(); await Promise.all([p1, p2]); console.log(x); // 1 або 2 - невизначено } // Правильний патерн для паралельних запитів: async function loadDashboard(userId) { const [user, posts, notifications] = await Promise.all([ fetchUser(userId), fetchPosts(userId), fetchNotifications(userId) ]); return { user, posts, notifications }; } ``` `Promise.all()` запускає всі три паралельно і повертає результати в тому ж порядку, в якому вони передані - незалежно від того, який Promise виконався першим. Функція `tricky()` вище - реальна гонка стану (race condition): обидва IIFE записують у `x` після одного такту мікрозавдань, і порядок непередбачуваний. Уникай спільного змінного стану в паралельних async операціях.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.