Оператор yield у генераторах
yield це оператор, який повертає значення з функції-генератора назовні та призупиняє її виконання до наступного виклику .next(). Коли генератор доходить до yield, він заморожується, а наступний .next() відновлює роботу одразу після того yield, де зупинився.
Теорія
TL;DR
yieldвіддає значення назовні у поліvalueі зупиняє функцію на місці.- Наступний
.next()продовжує виконання з рядка після цьогоyield. - Це «тимчасовий
return»: значення повернуто, але функція не завершена. next(value)передає значення назад: воно стає результатом виразуyield.yield*делегує виконання іншому генератору або будь-якому ітерованому об'єкту.- Завдяки
yieldобчислення ліниві: значення створюються лише на запит.
Швидкий приклад
function* gen() {
console.log('До першого yield');
yield 1;
console.log('Між yield');
yield 2;
console.log('Після другого yield');
}
const it = gen();
console.log(it.next()); // { value: 1, done: false }
console.log(it.next()); // { value: 2, done: false }
console.log(it.next()); // { value: undefined, done: true }Що відбувається покроково:
gen()повертає об'єкт-ітератор, функція ще не запущена.it.next()запускає функцію, вона доходить доyield 1, повертає{ value: 1 }і заморожується.- Наступний
it.next()продовжує з того самого місця, післяyield 1. - Після другого
yieldгенератор знову заморожується. - Коли приходить
done: true, генератор завершив роботу.
yield як тимчасовий return
function* seq() {
yield 'A';
yield 'B';
yield 'C';
}
const it = seq();
console.log(it.next().value); // "A"
console.log(it.next().value); // "B"
console.log(it.next().value); // "C"
console.log(it.next().done); // trueРізниця з return принципова: return завершує функцію остаточно, а yield лише віддає проміжне значення і залишає функцію живою, з усіма її локальними змінними та позицією виконання.
Передавання значення назад через next(value)
yield не тільки віддає дані назовні, а й може приймати значення назад, під час наступного виклику .next(value).
function* dialog() {
const name = yield 'Як тебе звати?';
yield `Привіт, ${name}!`;
}
const chat = dialog();
console.log(chat.next().value); // "Як тебе звати?"
console.log(chat.next('Maria').value); // "Привіт, Maria!"- Перший
next()запустив генератор і дійшов до першогоyield. - Другий
next('Maria')передав'Maria'у генератор, і це значення потрапило вconst name.
Завершення генератора: return() і throw()
Генератор завершується, коли доходить до кінця тіла, або якщо викликати iterator.return(value). У другому випадку він одразу віддає done: true.
function* numbers() {
yield 1;
yield 2;
}
const it = numbers();
console.log(it.next()); // { value: 1, done: false }
console.log(it.return('stop')); // { value: 'stop', done: true }
console.log(it.next()); // { value: undefined, done: true }Так само можна «вкинути» помилку прямо всередину генератора, у точку поточного yield, і перехопити її там звичайним try/catch:
function* gen() {
try {
yield 1;
} catch (e) {
console.log('Помилку перехоплено:', e.message);
}
}
const it = gen();
it.next();
it.throw(new Error('Щось пішло не так')); // Помилку перехоплено: Щось пішло не такyield*: делегування іншому генератору
Іноді один генератор має «передати керування» іншому. Для цього є yield*.
function* inner() {
yield 'B';
yield 'C';
}
function* outer() {
yield 'A';
yield* inner(); // передає керування іншому генератору
yield 'D';
}
for (const val of outer()) {
console.log(val);
}Вивід:
A
B
C
DПо суті yield* це вкладений цикл: «просто віддай усі значення іншого генератора». Він працює не лише з генераторами, а з будь-яким ітерованим об'єктом, наприклад yield* [1, 2, 3].
Лінивість обчислень
yield робить генератори лінивими (lazy): значення обчислюються тільки на запит, а не заздалегідь.
function* range(start, end) {
for (let i = start; i <= end; i++) {
console.log('Генерую', i);
yield i;
}
}
for (const num of range(1, 3)) {
console.log('Отримано:', num);
}Вивід:
Генерую 1
Отримано: 1
Генерую 2
Отримано: 2
Генерую 3
Отримано: 3Видно, що генератор створює значення в міру потреби, а не зберігає весь діапазон одразу.
Підсумок:
| Особливість | Опис |
|---|---|
| Що робить | Призупиняє виконання і повертає значення |
| Відновлення | Через .next() |
| Можна передавати значення назад | Так, next(value) |
| Можна завершити | Через return() |
| Можна делегувати | Через yield* |
| Обчислення | Ліниві |
| Працює тільки в | function* (генераторах) |
Типові помилки
- Використовувати
yieldпоза генератором. У звичайній чи стрілковій функції це синтаксична помилка,yieldіснує лише всерединіfunction*. - Чекати значення від першого
next(value). Перший виклик лише доводить виконання до першогоyield, тому переданий аргумент нікуди не потрапляє. - Плутати
yieldіyield*.yield inner()віддасть сам об'єкт-генератор одним значенням, аyield* inner()віддасть усі його значення по черзі. - Думати, що
yieldзавершує функцію. Ні, він лише ставить її на паузу; завершуєreturnабо вичерпання тіла. - Забувати про
try/catchнавколоyield. Без ньогоit.throw()просто завершить генератор і пробросить помилку назовні. - Створювати весь масив заздалегідь там, де потрібен
yield. Це втрачає головну перевагу генераторів, лінивість і економію пам'яті.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.