Yield: примеры (JAVASCRIPT)

Использование ключевого слова yield для функций генераторов в JavaScript
Раздел: Итераторы, Генераторы
yield(expression?: any): any

Базовая информация о yield в JavaScript

Ключевое слово yield используется внутри функций-генераторов для приостановки и возобновления их выполнения. Генераторная функция объявляется с помощью специального синтаксиса function*.

Yield применяется, когда требуется создание ленивых вычислений, работа с большими или бесконечными последовательностями данных, управление состоянием между вызовами или упрощение асинхронного кода (вместе с yield* для делегирования).

Аргументы и возвращаемые значения

Выражение yield может принимать опциональное значение, которое возвращается во внешний код: yield expression;.

При следующем вызове generator.next(value) переданное значение становится результатом этого yield внутри генератора. Сами методы генератора возвращают объект с двумя свойствами: value (значение, переданное в yield) и done (флаг завершения генератора).

Выражение yield* используется для делегирования выполнения другому итерируемому объекту или генератору, перебирая его элементы.

Простые примеры применения yield

Пример базового генератора чисел:

function* numberGenerator() {
  yield 1;
  yield 2;
  yield 3;
}
const gen = numberGenerator();
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 }
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: undefined, done: true }

Пример с передачей значения обратно в генератор через next():

function* greetingGenerator() {
  const name = yield 'Как вас зовут?';
  yield `Привет, ${name}!`;
}
const greetGen = greetingGenerator();
console.log(greetGen.next().value); // 'Как вас зовут?'
console.log(greetGen.next('Анна').value); // 'Привет, Анна!'
Как вас зовут?
Привет, Анна!

Пример делегирования с yield*:

function* generatorA() {
  yield 'a';
  yield 'b';
}
function* generatorB() {
  yield* generatorA();
  yield 'c';
}
for (let val of generatorB()) {
  console.log(val);
}
a
b
c

Похожие возможности в JavaScript

Итераторы — объекты с методом next(), возвращающим {value, done}. Любой генератор является итератором, но итератор можно создать вручную без yield.

Асинхронные функции (async/await) — синтаксический сахар для работы с промисами. Они похожи на комбинацию генераторов и промисов, но более специализированы для асинхронного кода. Асинхронные генераторы (async function*) используют yield для асинхронной выдачи значений.

Методы массивов (map, filter) — для преобразования конечных коллекций. Генераторы предпочтительнее при работе с потенциально бесконечными последовательностями или когда нужно ленивое вычисление каждого следующего элемента.

Аналоги yield в других языках

Python: использует такое же ключевое слово yield в функциях-генераторах. Синтаксис очень похож.

def number_generator():
    yield 1
    yield 2
    yield 3

for num in number_generator():
    print(num)
1
2
3

PHP: с версии 8.1 появились генераторы с синтаксисом yield, аналогичным JavaScript.

function numberGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

foreach (numberGenerator() as $number) {
    echo $number;
}
123

C#: использует yield return и yield break в методах, возвращающих IEnumerable или IEnumerator.

IEnumerable NumberGenerator() {
    yield return 1;
    yield return 2;
    yield return 3;
}

Kotlin: использует ключевое слово yield в контексте корутин, но как функцию (yield(value)), а не оператор.

Типичные ошибки при работе с yield

Использование yield вне функции-генератора приводит к синтаксической ошибке.

function normalFunction() {
  yield 1; // SyntaxError: Unexpected number
}
SyntaxError: Unexpected number

Попытка использования yield в стрелочных функциях (кроме специальных генераторных стрелочных функций).

const gen = () => {
  yield 1; // SyntaxError
};
SyntaxError: Unexpected string

Забывают, что первый вызов next() запускает генератор до первого yield, и нельзя передать начальное значение кроме как через аргумент функции-генератора.

function* gen(x) {
  const y = yield x;
  console.log(y);
}
const g = gen(10);
g.next(); // Запуск до первого yield
console.log(g.next(20).value); // Передаем значение первому yield
20
undefined

История изменений и стандартизации

Ключевое слово yield было введено в ECMAScript 6 (ES2015) вместе с функциями-генераторами.

В ECMAScript 2017 добавлены асинхронные функции и итераторы, но они не изменили поведение самого yield.

ECMAScript 2018 представил асинхронные генераторы (async function*), где yield может использоваться для возврата промисов, и выражение yield* получило возможность работать с асинхронными итерируемыми объектами.

Расширенные примеры использования yield

Реализация бесконечной последовательности (например, чисел Фибоначчи):

Пример javascript
function* fibonacci() {
  let [prev, curr] = [0, 1];
  while (true) {
    yield curr;
    [prev, curr] = [curr, prev + curr];
  }
}
const fib = fibonacci();
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3
1
1
2
3

Имитация асинхронных операций с помощью генераторов и промисов (упрощенный аналог async/await):

Пример javascript
function* asyncLikeGenerator() {
  const data = yield new Promise(res => setTimeout(() => res('Данные'), 100));
  console.log('Получено:', data);
}
const asyncGen = asyncLikeGenerator();
const promise = asyncGen.next().value;
promise.then(result => {
  asyncGen.next(result);
});
// Через 100 мс:
Получено: Данные

Генератор для обхода дерева с использованием yield*:

Пример javascript
function* traverseTree(node) {
  if (!node) return;
  yield node.value;
  yield* traverseTree(node.left);
  yield* traverseTree(node.right);
}
const tree = {
  value: 1,
  left: { value: 2, left: null, right: null },
  right: { value: 3, left: null, right: null }
};
for (let val of traverseTree(tree)) {
  console.log(val);
}
1
2
3

Использование return в генераторе:

Пример javascript
function* genWithReturn() {
  yield 1;
  yield 2;
  return 'завершено';
}
const g = genWithReturn();
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: 'завершено', done: true }
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 'завершено', done: true }

JS yield function comments

En
Yield Pauses and resumes a generator function.