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); // Передаем значение первому yield20 undefined
История изменений и стандартизации
Ключевое слово yield было введено в ECMAScript 6 (ES2015) вместе с функциями-генераторами.
В ECMAScript 2017 добавлены асинхронные функции и итераторы, но они не изменили поведение самого yield.
ECMAScript 2018 представил асинхронные генераторы (async function*), где yield может использоваться для возврата промисов, и выражение yield* получило возможность работать с асинхронными итерируемыми объектами.
Расширенные примеры использования yield
Реализация бесконечной последовательности (например, чисел Фибоначчи):
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); // 31 1 2 3
Имитация асинхронных операций с помощью генераторов и промисов (упрощенный аналог async/await):
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*:
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 в генераторе:
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 }