Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Генератори та yield». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Генератор це функція, оголошена через `function*`, яка вміє призупиняти власне виконання і згодом продовжувати його з того самого місця.** Її виклик не виконує тіло, а повертає об’єкт-ітератор. Кожен `yield` призупиняє функцію і віддає значення назовні у вигляді `{ value, done }`, а наступний виклик `.next(value)` відновлює роботу і повертає передане значення всередину, саме туди, де стояв `yield`. Додатково `.return(value)` примусово завершує генератор, а `.throw(error)` кидає в нього виняток. ```javascript function* counter() { yield 1; yield 2; } const gen = counter(); gen.next(); // { value: 1, done: false } ``` **Ключове:** генератор віддає значення назовні через `yield`, а приймає їх назад через аргумент `.next(value)`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Генератор (`function*`) це функція, яка може призупиняти своє виконання, віддавати проміжні значення і потім продовжувати роботу з того самого місця.** Формально `function*` повертає спеціальний об’єкт, ітератор, який керує виконанням тіла функції. ## Теорія ### TL;DR - `function*` оголошує генератор; його виклик не виконує код, а повертає об’єкт-ітератор. - `yield` призупиняє виконання, віддає значення назовні у `{ value, done }` і чекає на наступний `.next()`. - `gen.next(value)` повертає значення всередину: воно стає результатом того `yield`, на якому генератор зупинився. - `.return(value)` завершує генератор достроково, `.throw(error)` кидає в нього виняток, який можна впіймати через `try...catch`. - Генератор автоматично реалізує і протокол ітератора (`next()`), і протокол ітерованого об’єкта (`[Symbol.iterator]`), тому працює у `for...of`. - Генератори ідеальні для лінивих обчислень і нескінченних послідовностей: дані видаються порціями, без створення величезних масивів. ### Швидкий приклад ```javascript function* myGenerator() { yield 1; yield 2; yield 3; } const gen = myGenerator(); console.log(gen.next()); // { value: 1, done: false } console.log(gen.next()); // { value: 2, done: false } console.log(gen.next()); // { value: 3, done: false } console.log(gen.next()); // { value: undefined, done: true } ``` ### Як оголошується генератор і що повертає його виклик Генератор оголошується зірочкою після ключового слова `function`: ```javascript function* myGenerator() { yield 1; yield 2; yield 3; } ``` Найважливіший момент: під час виклику `myGenerator()` сам код функції не виконується. Виклик повертає ітератор, який треба «прокручувати» вручну, викликаючи `.next()`. Кожен такий виклик доводить функцію до наступного `yield` і повертає об’єкт `{ value, done }`, де `value` це віддане значення, а `done` показує, чи функція вже дійшла до кінця. Генератором може бути і метод об’єкта чи класу, для цього зірочку ставлять перед іменем: `*items() { ... }`. А от стрілкова функція генератором бути не може. ### Що робить yield Оператор `yield` робить три речі: 1. **Призупиняє виконання** функції рівно на цьому місці. 2. **Віддає значення назовні**, у результат `.next()`. 3. Дозволяє **передати значення назад усередину** під час наступного виклику `.next()`. **Приклад 1, проста послідовність:** ```javascript function* counter() { yield 1; yield 2; yield 3; } const gen = counter(); console.log(gen.next()); // { value: 1, done: false } console.log(gen.next()); // { value: 2, done: false } console.log(gen.next()); // { value: 3, done: false } console.log(gen.next()); // { value: undefined, done: true } ``` **Приклад 2, паузи між кроками:** ```javascript function* steps() { console.log("Крок 1"); yield; console.log("Крок 2"); yield; console.log("Крок 3"); } const gen = steps(); gen.next(); // "Крок 1" gen.next(); // "Крок 2" gen.next(); // "Крок 3" ``` Тут `yield` використовується без значення, просто як точка зупинки. ### Як передати значення назад у генератор Коли ви викликаєте `gen.next(value)`, цей `value` потрапляє всередину генератора саме в те місце, де виконання було призупинене оператором `yield`. Тобто вираз `yield "..."` обчислюється в те, що ви передали наступним `.next()`. **Приклад 3, передача значення назад:** ```javascript function* dialog() { const name = yield "Як тебе звати?"; const age = yield `Привіт, ${name}! Скільки тобі років?`; yield `Тобі ${age} років.`; } const gen = dialog(); console.log(gen.next()); // { value: "Як тебе звати?", done: false } console.log(gen.next("Maria")); // { value: "Привіт, Maria! Скільки тобі років?", done: false } console.log(gen.next(25)); // { value: "Тобі 25 років.", done: false } console.log(gen.next()); // { value: undefined, done: true } ``` Пояснення по кроках: - Перший `next()` запускає генератор і повертає значення першого `yield`. - Другий `next("Maria")` вставляє рядок `"Maria"` туди, де стояв перший `yield`, тому `name` отримує це значення. - Третій `next(25)` вставляє `25` на місце другого `yield`, і `age` дорівнює `25`. ### Керування генератором: next, return і throw У ітератора генератора є три методи керування: | Метод | Що робить | | --- | --- | | `.next(value)` | Продовжує виконання і повертає наступний `{ value, done }` | | `.return(value)` | Примусово завершує генератор, повертає `{ value, done: true }` | | `.throw(error)` | Кидає виняток усередину генератора, його можна впіймати через `try...catch` | **Приклад 5, `return()` і `throw()`:** ```javascript function* process() { try { yield "Крок 1"; yield "Крок 2"; } catch (e) { console.log("Помилка:", e.message); } finally { console.log("Генератор завершено"); } } const gen = process(); console.log(gen.next()); // { value: "Крок 1", done: false } console.log(gen.throw(new Error("Щось пішло не так"))); // Помилка: Щось пішло не так // Генератор завершено ``` Блок `finally` спрацьовує і тоді, коли генератор завершують через `.return()`, тому саме туди зручно класти прибирання ресурсів. ### Генератори як ітератори, ліниві та асинхронні послідовності Генератор автоматично реалізує інтерфейс ітератора (`next()`) і інтерфейс ітерованого об’єкта (`[Symbol.iterator]`), тому його можна напряму передати у `for...of`, у spread або в деструктуризацію: ```javascript function* numbers() { yield 1; yield 2; yield 3; } for (const n of numbers()) { console.log(n); // 1, 2, 3 } ``` **Приклад 4, нескінченна послідовність:** ```javascript function* infinite() { let i = 0; while (true) { yield i++; } } const gen = infinite(); console.log(gen.next().value); // 0 console.log(gen.next().value); // 1 console.log(gen.next().value); // 2 ``` Такий код не зависає, бо значення обчислюються ліниво: наступне число з’являється лише тоді, коли його попросили через `.next()`. **Бонус, генератори і асинхронність.** До появи `async/await` генератори використовували саме для асинхронного коду (через бібліотеки на кшталт `co`): ```javascript function* getData() { const user = yield fetch('/user'); const posts = yield fetch(`/posts?user=${user.id}`); return posts; } ``` Пізніше на цій самій ідеї і побудували `async/await`, по суті це «автоматизовані генератори», де рушій сам викликає `.next()`, коли проміс виконався. **Підсумкова таблиця:** | Поняття | Опис | | --- | --- | | `function*` | Оголошення генератора | | `yield` | Призупиняє виконання і віддає значення назовні | | `.next(value)` | Відновлює виконання, передаючи `value` всередину | | `.return(value)` | Завершує генератор | | `.throw(error)` | Кидає виняток у генератор | | **Генератор** | Об’єкт-ітератор, виконання якого можна призупиняти і продовжувати | ### Типові помилки - **Думати, що виклик `myGenerator()` виконує тіло функції.** Він лише створює ітератор; перший рядок коду виконається тільки на першому `.next()`. - **Очікувати, що аргумент першого `.next(value)` кудись потрапить.** Перший виклик лише запускає генератор, і передане значення ігнорується, бо ще немає `yield`, який міг би його прийняти. - **Плутати напрямки.** Назовні летить значення праворуч від `yield`, а всередину повертається аргумент наступного `.next()`. - **Прогонити нескінченний генератор через `for...of` без `break`.** Цикл ніколи не завершиться, бо `done` не стане `true`. - **Забувати, що генератор одноразовий.** Після `done: true` або після `.return()` усі наступні `.next()` дають `{ value: undefined, done: true }`; щоб пройти послідовність ще раз, треба створити новий генератор. - **Намагатися оголосити генератор стрілковою функцією.** `function*` працює лише зі звичайними функціями та методами (`*method() {}`). - **Вважати генератор асинхронним сам по собі.** Звичайний генератор синхронний; для асинхронних послідовностей потрібні `async function*` і `for await...of`.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.