Що повертає виклик генератора
Виклик функції-генератора (function*) не запускає її код одразу: він повертає об'єкт-генератор, який є ітератором і керує виконанням цієї функції. Тобто ви отримуєте не результат, а засіб, щоб виконувати тіло функції порціями.
Теорія
TL;DR
gen()не виконує жодного рядка тіла, він створює об'єкт-генератор.- Цей об'єкт є ітератором: у нього є
.next(),.throw()і.return(). - Одночасно він є ітерованим об'єктом, бо має
[Symbol.iterator](). - Кожен
.next()повертає{ value, done }і доводить виконання до наступногоyield. - Щоб отримати всі значення одразу, генератор треба обійти:
for...of, спред,Array.from. - Генератор не дорівнює результату функції:
numbers()це не[1, 2].
Швидкий приклад
function* gen() {
yield 1;
yield 2;
yield 3;
}
const iterator = gen(); // повертається об'єкт-генератор
console.log(iterator); // Object [Generator] {}Об'єкт iterator це не результат функції, а контролер, який дозволяє виконувати генератор покроково через .next(), .throw(), .return().
Два протоколи, які реалізує генератор
Об'єкт-генератор одночасно реалізує два протоколи:
- Протокол ітератора: метод
next(), який повертає об'єкт з полямиvalueіdone. - Протокол ітерованого об'єкта: метод
[Symbol.iterator](), який повертає сам генератор.
Саме тому той самий об'єкт можна і крутити вручну через .next(), і передати у for...of.
function* gen() {
yield 'A';
yield 'B';
}
const it = gen();
console.log(it.next()); // { value: 'A', done: false }
console.log(it.next()); // { value: 'B', done: false }
console.log(it.next()); // { value: undefined, done: true }Кожен виклик .next():
- запускає генератор, якщо той «заморожений»;
- виконує його до наступного
yieldабоreturn; - повертає об'єкт
{ value, done }.
Генератор у for...of, спреді та Array.from
Оскільки генератор ітерований, його можна використовувати всюди, де очікується ітерований об'єкт:
function* gen() {
yield 1;
yield 2;
yield 3;
}
const iterator = gen();
for (const n of iterator) {
console.log(n);
}Вивід:
1
2
3Так само працюють [...gen()] і Array.from(gen()). Важливо: обхід вичерпує генератор, тому повторний for...of по тому самому об'єкту не дасть жодного значення, потрібен новий виклик gen().
Три методи об'єкта-генератора
| Метод | Що робить |
|---|---|
.next(value) | Відновлює виконання до наступного yield; повертає { value, done } |
.throw(error) | Кидає виняток усередину генератора (ловиться через try/catch) |
.return(value) | Завершує генератор і повертає { value, done: true } |
Генератор це не результат функції
Про це найчастіше забувають:
function* numbers() {
yield 1;
yield 2;
}
const result = numbers(); // повертає не [1, 2], а об'єкт-генераторЩоб отримати справжні значення, генератор треба обійти:
console.log([...numbers()]); // [1, 2]або вручну викликати .next() кілька разів.
Схематично це виглядає так:
┌─────────────────────┐
│ function* numbers() │
│ { yield 1; yield 2; }│
└────────┬────────────┘
│ виклик
▼
┌───────────────────────────────┐
│ Object [Generator] │
│ ├── next() │
│ ├── throw() │
│ ├── return() │
│ └── [Symbol.iterator]() │
└───────────────────────────────┘Підсумок:
| Що повертає генератор | Об'єкт-генератор |
|---|---|
| Запускається одразу? | Ні |
| Чи можна керувати виконанням? | Так, через .next() |
| Чи реалізує протокол ітератора? | Так |
| Як повертає значення? | Через yield |
Чи можна використати у for...of | Так |
| Як повертає кінцеве значення | Через return або разом з done: true |
Типові помилки
- Очікувати масив.
numbers()повертає об'єкт-генератор, а не[1, 2]; масив дає спред абоArray.from. - Думати, що тіло вже виконалося. Навіть
console.logна початку генератора не спрацює, доки не буде першого.next(). - Двічі обходити той самий об'єкт-генератор. Після
done: trueвін вичерпаний; щоб пройти знову, викликайте функцію-генератор ще раз. - Плутати значення
yieldі значенняreturn. Те, що повернувreturn, приходить разом зdone: trueі не потрапляє уfor...of. - Забувати, що
.return()завершує генератор назавжди. Наступні.next()віддаватимуть лише{ value: undefined, done: true }. - Перевіряти
typeof gen(). Це завжди'object', тому перевіряти тип так безглуздо; ознакою генератора є наявність методуnextі[Symbol.iterator].
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.