Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Асинхронний генератор». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`async function*` це функція, яка поступово віддає асинхронні значення.** Вона створює асинхронний ітератор, у якого `.next()` повертає не звичайний об’єкт, а `Promise`, що резолвиться в `{ value, done }`. Усередині можна вільно поєднувати `await` (чекати на запит, таймер, читання файлу) і `yield` (віддати чергову порцію назовні). Перебирати такий генератор можна лише через `for await...of`, бо він реалізує `[Symbol.asyncIterator]`, а не `[Symbol.iterator]`. ```javascript async function* numbers() { for (let i = 1; i <= 3; i++) { await new Promise(r => setTimeout(r, 1000)); yield i; } } for await (const n of numbers()) console.log(n); // 1, 2, 3 ``` **Ключове:** `function*` дає синхронну послідовність, `async function*` дає асинхронну, по одному значенню за раз, і читається через `for await...of`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Асинхронний генератор (`async function*`) це функція, яка поступово повертає асинхронні значення.** Вона створює асинхронний ітератор, метод `.next()` якого повертає `Promise`, а не звичайний об’єкт `{ value, done }`. ## Теорія ### TL;DR - Оголошення: `async function* name() { ... }`, тобто `async` плюс зірочка. - Повертає асинхронний ітератор; `.next()` дає `Promise<{ value, done }>`. - Усередині дозволені і `await`, і `yield`, тому між кроками можна робити запити, паузи, читання з бази. - Перебирається тільки через `for await...of`; звичайний `for...of` з ним не працює, бо реалізовано `[Symbol.asyncIterator]`. - Підтримує делегування через `yield*`, а також методи керування `.return()` і `.throw()`. - Типове застосування: стрімінг даних, API з пагінацією, ліниві завантаження, асинхронні пайплайни. ### Швидкий приклад ```javascript async function* fetchNumbers() { yield 1; yield 2; yield 3; } (async () => { for await (const n of fetchNumbers()) { console.log(n); } })(); ``` Вивід: ```javascript 1 2 3 ``` Відмінність від звичайного `function*`: кожне значення повертається асинхронно, через `Promise`, тому працювати з ним можна лише через `for await...of`. ### Асинхронний yield: очікування між кроками Асинхронні генератори корисні саме тоді, коли між ітераціями є асинхронні дії: `fetch`, `await`, `setTimeout`, запити до бази даних і подібне. ```javascript async function* delayedNumbers() { for (let i = 1; i <= 3; i++) { await new Promise(resolve => setTimeout(resolve, 1000)); // чекаємо 1 секунду yield i; } } (async () => { for await (const n of delayedNumbers()) { console.log(n); } })(); ``` Вивід (з паузою в одну секунду між числами): ```javascript 1 2 3 ``` Тут генератор асинхронно чекає всередині `await`, а потім віддає наступне значення через `yield`. Цикл `for await...of` на кожній ітерації неявно робить `await` над результатом `.next()`. ### Що повертає асинхронний генератор Якщо крутити ітератор вручну, кожен `.next()` треба чекати: ```javascript const gen = delayedNumbers(); console.log(await gen.next()); // { value: 1, done: false } console.log(await gen.next()); // { value: 2, done: false } console.log(await gen.next()); // { value: 3, done: false } console.log(await gen.next()); // { value: undefined, done: true } ``` > `gen.next()` повертає `Promise`, який резолвиться в `{ value, done }`. Асинхронний генератор повертає об’єкт із методом: ```javascript [Symbol.asyncIterator]() ``` який повертає сам себе. Такий об’єкт можна перебирати лише через `for await...of`, а не через `for...of`. Як і звичайний генератор, асинхронний підтримує делегування через `yield*`, тобто вкладення іншого асинхронного генератора: ```javascript async function* sub() { yield 1; yield 2; } async function* main() { yield* sub(); // делегує ітерації yield 3; } for await (const v of main()) { console.log(v); } ``` Вивід: ```javascript 1 2 3 ``` Працюють і методи керування `.return()` та `.throw()`: ```javascript async function* gen() { try { yield 1; yield 2; } catch (e) { console.log('Помилка всередині:', e.message); } finally { console.log('Генератор завершено'); } } const iterator = gen(); console.log(await iterator.next()); // { value: 1, done: false } console.log(await iterator.throw(new Error("fail"))); // Помилка всередині: fail // Генератор завершено ``` ### Потік даних і асинхронні пайплайни Асинхронні генератори ідеально підходять для потокової обробки даних: ```javascript async function* streamData(urls) { for (const url of urls) { const res = await fetch(url); const data = await res.json(); yield data; } } (async () => { const urls = [ '/api/user', '/api/posts', '/api/comments' ]; for await (const chunk of streamData(urls)) { console.log('Дані:', chunk); } })(); ``` Тут кожен `fetch()` виконується по черзі, а після кожного `yield` дані віддаються назовні, у цикл `for await...of`. Кілька генераторів можна складати один в одного, як ланки конвеєра: ```javascript async function* generateNumbers() { for (let i = 1; i <= 10; i++) { await new Promise(r => setTimeout(r, 200)); yield i; } } async function* filterEven(source) { for await (const n of source) { if (n % 2 === 0) yield n; } } async function* double(source) { for await (const n of source) { yield n * 2; } } // Комбінуємо кілька асинхронних генераторів: (async () => { const pipeline = double(filterEven(generateNumbers())); for await (const value of pipeline) { console.log(value); // 4, 8, 12, 16, 20 } })(); ``` Це і є лінива асинхронна обробка даних, по суті потокова пайплайн-система (stream pipeline): нічого не обчислюється наперед, кожна ланка тягне наступне значення лише тоді, коли його попросили. ### Де це застосовують і підсумок | Сценарій | Як використовується | | --- | --- | | **Стрімінг даних** | Поступове завантаження частин файлу або відповіді сервера | | **Поступові запити** | Робота з API, яке повертає дані «сторінками» | | **Ліниві обчислення** | Генерація значень у міру потреби | | **Асинхронні пайплайни** | Комбінування кількох джерел даних | | **Node.js Streams** | Сумісність з інтерфейсами `ReadableStream` та `AsyncIterator` | Підсумок: | Особливість | Опис | | --- | --- | | Оголошення | `async function* name() { ... }` | | Повертає | Асинхронний ітератор | | Метод `.next()` | Повертає `Promise<{ value, done }>` | | Використовується з | `for await...of` | | `yield` | Асинхронно віддає проміжні значення | | Застосування | Потоки даних, API з пагінацією, ліниві завантаження, генерація подій | Головна ідея коротко: `function*` дає синхронні послідовності, а `async function*` дає асинхронні, наприклад «по одному запиту за раз». ### Типові помилки - **Перебирати асинхронний генератор через `for...of`.** Буде помилка, бо об’єкт реалізує `[Symbol.asyncIterator]`, а не `[Symbol.iterator]`; потрібен `for await...of`. - **Забувати `await` перед `.next()`.** Без нього ви отримаєте `Promise`, а не `{ value, done }`, і перевірка `done` завжди буде `undefined`. - **Використовувати `for await...of` поза асинхронним контекстом.** Він працює лише всередині `async`-функції або в модулі з підтримкою top-level `await`. - **Очікувати паралельності.** Асинхронний генератор послідовний: наступний `fetch` стартує лише після того, як попереднє значення забрали. Для паралельних запитів потрібен `Promise.all`, а не генератор. - **Ставити `yield` усередині `forEach` або іншого колбека.** `yield` працює тільки в тілі самого генератора, тому потрібні звичайні цикли `for` чи `for await...of`. - **Не закривати генератор.** Якщо вийти з циклу через `break` або `return`, рушій викличе `.return()`, тому ресурси (з’єднання, потоки) варто звільняти у блоці `finally`.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.