yield
Ключевое слово yield используется для остановки и возобновления функций-генераторов ( function* или legacy generator function (en-US) ).
Интерактивный пример
Синтаксис
[rv] = yield [[выражение]];
Возвращаемое выражение. Если не указано, то возвращается значение undefined .
Возвращает необязательное значение, которое передаётся в next() генератора, чтобы возобновить его выполнение.
Описание
Ключевое слово yield вызывает остановку функции-генератора и возвращает текущее значение выражения, указанного после ключевого слова yield . Его можно рассматривать как аналог ключевого слова return в функции-генераторе.
На самом деле ключевое слово yield возвращает объект с двумя параметрами, value и done . При этом, value является результатом вычисления выражения после yield , а done указывает, была ли завершена функция-генератор.
Во время остановки на операторе yield , выполнение кода в функции-генераторе не возобновится, пока не будет вызван метод next() возвращаемого функцией объекта-генератора. Это предоставляет непосредственный контроль за выполнением генератора и возвратом его значений.
Примеры
Следующий фрагмент кода содержит определение функции-генератора и вспомогательной функции:
function* foo() var index = 0; while (index 2) // при достижении 2, done в yield станет true, а value undefined; yield index++; >
После того как тело функции-генератора определено, оно может использоваться для получения итератора:
var iterator = foo(); console.log(iterator.next()); // console.log(iterator.next()); // console.log(iterator.next()); // console.log(iterator.next()); //
Спецификации
| Specification |
|---|
| ECMAScript Language Specification # prod-YieldExpression |
Совместимость с браузерами
BCD tables only load in the browser
Смотрите также
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
This page was last modified on 7 авг. 2023 г. by MDN contributors.
Your blueprint for a better internet.
MDN
Support
- Product help
- Report an issue
Our communities
Developers
- Web Technologies
- Learn Web Development
- MDN Plus
- Hacks Blog
- Website Privacy Notice
- Cookies
- Legal
- Community Participation Guidelines
Visit Mozilla Corporation’s not-for-profit parent, the Mozilla Foundation.
Portions of this content are ©1998– 2024 by individual mozilla.org contributors. Content available under a Creative Commons license.
Генераторы
Обычные функции возвращают только одно-единственное значение (или ничего).
Генераторы могут порождать (yield) множество значений одно за другим, по мере необходимости. Генераторы отлично работают с перебираемыми объектами и позволяют легко создавать потоки данных.
Функция-генератор
Для объявления генератора используется специальная синтаксическая конструкция: function* , которая называется «функция-генератор».
Выглядит она так:
function* generateSequence()
Функции-генераторы ведут себя не так, как обычные. Когда такая функция вызвана, она не выполняет свой код. Вместо этого она возвращает специальный объект, так называемый «генератор», для управления её выполнением.
function* generateSequence() < yield 1; yield 2; return 3; >// "функция-генератор" создаёт объект "генератор" let generator = generateSequence(); alert(generator); // [object Generator]
Выполнение кода функции ещё не началось:
Основным методом генератора является next() . При вызове он запускает выполнение кода до ближайшей инструкции yield (значение может отсутствовать, в этом случае оно предполагается равным undefined ). По достижении yield выполнение функции приостанавливается, а соответствующее значение – возвращается во внешний код:
Результатом метода next() всегда является объект с двумя свойствами:
- value : значение из yield .
- done : true , если выполнение функции завершено, иначе false .
Например, здесь мы создаём генератор и получаем первое из возвращаемых им значений:
function* generateSequence() < yield 1; yield 2; return 3; >let generator = generateSequence(); let one = generator.next(); alert(JSON.stringify(one)); //
На данный момент мы получили только первое значение, выполнение функции остановлено на второй строке:
Повторный вызов generator.next() возобновит выполнение кода и вернёт результат следующего yield :
let two = generator.next(); alert(JSON.stringify(two)); //
И, наконец, последний вызов завершит выполнение функции и вернёт результат return :
let three = generator.next(); alert(JSON.stringify(three)); //
Сейчас генератор полностью выполнен. Мы можем увидеть это по свойству done:true и обработать value:3 как окончательный результат.
Новые вызовы generator.next() больше не имеют смысла. Впрочем, если они и будут, то не вызовут ошибки, но будут возвращать один и тот же объект: .
function* f(…) или function *f(…) ?
Нет разницы, оба синтаксиса корректны.
Но обычно предпочтителен первый вариант, так как звёздочка относится к типу объявляемой сущности ( function* – «функция-генератор»), а не к её названию, так что резонно расположить её у слова function .
Перебор генераторов
Как вы, наверное, уже догадались по наличию метода next() , генераторы являются перебираемыми объектами.
Возвращаемые ими значения можно перебирать через for..of :
function* generateSequence() < yield 1; yield 2; return 3; >let generator = generateSequence(); for(let value of generator) < alert(value); // 1, затем 2 >
Выглядит гораздо красивее, чем использование .next().value , верно?
…Но обратите внимание: пример выше выводит значение 1 , затем 2 . Значение 3 выведено не будет!
Это из-за того, что перебор через for..of игнорирует последнее значение, при котором done: true . Поэтому, если мы хотим, чтобы были все значения при переборе через for..of , то надо возвращать их через yield :
function* generateSequence() < yield 1; yield 2; yield 3; >let generator = generateSequence(); for(let value of generator) < alert(value); // 1, затем 2, затем 3 >
Так как генераторы являются перебираемыми объектами, мы можем использовать всю связанную с ними функциональность, например оператор расширения . :
function* generateSequence() < yield 1; yield 2; yield 3; >let sequence = [0, . generateSequence()]; alert(sequence); // 0, 1, 2, 3
В коде выше . generateSequence() превращает перебираемый объект-генератор в массив элементов (подробнее ознакомиться с оператором расширения можно в главе Остаточные параметры и оператор расширения)
Использование генераторов для перебираемых объектов
Некоторое время назад, в главе Перебираемые объекты, мы создали перебираемый объект range , который возвращает значения from..to .
Давайте вспомним код:
let range = < from: 1, to: 5, // for..of range вызывает этот метод один раз в самом начале [Symbol.iterator]() < // . он возвращает перебираемый объект: // далее for..of работает только с этим объектом, запрашивая следующие значения return < current: this.from, last: this.to, // next() вызывается при каждой итерации цикла for..of next() < // нужно вернуть значение как объект if (this.current ; > else < return < done: true >; > > >; > >; // при переборе объекта range будут выведены числа от range.from до range.to alert([. range]); // 1,2,3,4,5
Мы можем использовать функцию-генератор для итерации, указав её в Symbol.iterator .
Вот тот же range , но с гораздо более компактным итератором:
let range = < from: 1, to: 5, *[Symbol.iterator]() < // краткая запись для [Symbol.iterator]: function*() for(let value = this.from; value > >; alert( [. range] ); // 1,2,3,4,5
Это работает, потому что range[Symbol.iterator]() теперь возвращает генератор, и его методы – в точности то, что ожидает for..of :
- у него есть метод .next()
- который возвращает значения в виде
Это не совпадение, конечно. Генераторы были добавлены в язык JavaScript, в частности, с целью упростить создание перебираемых объектов.
Вариант с генератором намного короче, чем исходный вариант перебираемого range , и сохраняет те же функциональные возможности.
Генераторы могут генерировать бесконечно
В примерах выше мы генерировали конечные последовательности, но мы также можем сделать генератор, который будет возвращать значения бесконечно. Например, бесконечная последовательность псевдослучайных чисел.
Конечно, нам потребуется break (или return ) в цикле for..of по такому генератору, иначе цикл будет продолжаться бесконечно, и скрипт «зависнет».
Композиция генераторов
Композиция генераторов – это особенная возможность генераторов, которая позволяет прозрачно «встраивать» генераторы друг в друга.
Например, у нас есть функция для генерации последовательности чисел:
function* generateSequence(start, end)
Мы хотели бы использовать её при генерации более сложной последовательности:
- сначала цифры 0..9 (с кодами символов 48…57)
- за которыми следуют буквы в верхнем регистре A..Z (коды символов 65…90)
- за которыми следуют буквы алфавита a..z (коды символов 97…122)
Мы можем использовать такую последовательность для генерации паролей, выбирать символы из неё (может быть, ещё добавить символы пунктуации), но сначала её нужно сгенерировать.
В обычной функции, чтобы объединить результаты из нескольких других функций, мы вызываем их, сохраняем промежуточные результаты, а затем в конце их объединяем.
Для генераторов есть особый синтаксис yield* , который позволяет «вкладывать» генераторы один в другой (осуществлять их композицию).
Вот генератор с композицией:
function* generateSequence(start, end) < for (let i = start; i function* generatePasswordCodes() < // 0..9 yield* generateSequence(48, 57); // A..Z yield* generateSequence(65, 90); // a..z yield* generateSequence(97, 122); >let str = ''; for(let code of generatePasswordCodes()) < str += String.fromCharCode(code); >alert(str); // 0..9A..Za..z
Директива yield* делегирует выполнение другому генератору. Этот термин означает, что yield* gen перебирает генератор gen и прозрачно направляет его вывод наружу. Как если бы значения были сгенерированы внешним генератором.
Результат – такой же, как если бы мы встроили код из вложенных генераторов:
function* generateSequence(start, end) < for (let i = start; i function* generateAlphaNum() < // yield* generateSequence(48, 57); for (let i = 48; i let str = ''; for(let code of generateAlphaNum()) < str += String.fromCharCode(code); >alert(str); // 0..9a..zA..Z
Композиция генераторов – естественный способ вставлять вывод одного генератора в поток другого. Она не использует дополнительную память для хранения промежуточных результатов.
yield – дорога в обе стороны
До этого момента генераторы сильно напоминали перебираемые объекты, со специальным синтаксисом для генерации значений. Но на самом деле они намного мощнее и гибче.
Всё дело в том, что yield – дорога в обе стороны: он не только возвращает результат наружу, но и может передавать значение извне в генератор.
Чтобы это сделать, нам нужно вызвать generator.next(arg) с аргументом. Этот аргумент становится результатом yield .
Продемонстрируем это на примере:
function* gen() < // Передаём вопрос во внешний код и ожидаем ответа let result = yield "2 + 2 = ?"; // (*) alert(result); >let generator = gen(); let question = generator.next().value; // передаём результат в генератор
- Первый вызов generator.next() – всегда без аргумента, он начинает выполнение и возвращает результат первого yield «2+2=?» . На этой точке генератор приостанавливает выполнение.
- Затем, как показано на картинке выше, результат yield переходит во внешний код в переменную question .
- При generator.next(4) выполнение генератора возобновляется, а 4 выходит из присваивания как результат: let result = 4 .
Обратите внимание, что внешний код не обязан немедленно вызывать next(4) . Ему может потребоваться время. Это не проблема, генератор подождёт.
// возобновить генератор через некоторое время setTimeout(() => generator.next(4), 1000);
Как видно, в отличие от обычных функций, генератор может обмениваться результатами с вызывающим кодом, передавая значения в next/yield .
Чтобы сделать происходящее более очевидным, вот ещё один пример с большим количеством вызовов:
function* gen() < let ask1 = yield "2 + 2 = ?"; alert(ask1); // 4 let ask2 = yield "3 * 3 = ?" alert(ask2); // 9 >let generator = gen(); alert( generator.next().value ); // "2 + 2 = ?" alert( generator.next(4).value ); // "3 * 3 = ?" alert( generator.next(9).done ); // true
- Первый .next() начинает выполнение… Оно доходит до первого yield .
- Результат возвращается во внешний код.
- Второй .next(4) передаёт 4 обратно в генератор как результат первого yield и возобновляет выполнение.
- …Оно доходит до второго yield , который станет результатом .next(4) .
- Третий next(9) передаёт 9 в генератор как результат второго yield и возобновляет выполнение, которое завершается окончанием функции, так что done: true .
Получается такой «пинг-понг»: каждый next(value) передаёт в генератор значение, которое становится результатом текущего yield , возобновляет выполнение и получает выражение из следующего yield .
generator.throw
Как мы видели в примерах выше, внешний код может передавать значение в генератор как результат yield .
…Но можно передать не только результат, но и инициировать ошибку. Это естественно, так как ошибка является своего рода результатом.
Для того, чтобы передать ошибку в yield , нам нужно вызвать generator.throw(err) . В таком случае исключение err возникнет на строке с yield .
Например, здесь yield «2 + 2 = ?» приведёт к ошибке:
function* gen() < try < let result = yield "2 + 2 = ?"; // (1) alert("Выполнение программы не дойдёт до этой строки, потому что выше возникнет исключение"); >catch(e) < alert(e); // покажет ошибку >> let generator = gen(); let question = generator.next().value; generator.throw(new Error("Ответ не найден в моей базе данных")); // (2)
Ошибка, которая проброшена в генератор на строке (2) , приводит к исключению на строке (1) с yield . В примере выше try..catch перехватывает её и отображает.
Если мы не хотим перехватывать её, то она, как и любое обычное исключение, «вывалится» из генератора во внешний код.
Текущая строка вызывающего кода – это строка с generator.throw , отмечена (2) . Таким образом, мы можем отловить её во внешнем коде, как здесь:
function* generate() < let result = yield "2 + 2 = ?"; // Ошибка в этой строке >let generator = generate(); let question = generator.next().value; try < generator.throw(new Error("Ответ не найден в моей базе данных")); >catch(e) < alert(e); // покажет ошибку >
Если же ошибка и там не перехвачена, то дальше – как обычно, она выпадает наружу и, если не перехвачена, «повалит» скрипт.
Итого
- Генераторы создаются при помощи функций-генераторов function* f(…) .
- Внутри генераторов и только внутри них существует оператор yield .
- Внешний код и генератор обмениваются промежуточными результатами посредством вызовов next/yield .
В современном JavaScript генераторы используются редко. Но иногда они оказываются полезными, потому что способность функции обмениваться данными с вызывающим кодом во время выполнения совершенно уникальна. И, конечно, для создания перебираемых объектов.
Также, в следующей главе мы будем изучать асинхронные генераторы, которые используются, чтобы читать потоки асинхронно сгенерированных данных (например, постранично загружаемые из сети) в цикле for await . of .
В веб-программировании мы часто работаем с потоками данных, так что это ещё один важный случай использования.
Задачи
Псевдослучайный генератор
Есть много областей, где нам нужны случайные данные.
Одной из них является тестирование. Нам могут понадобиться случайные данные: текст, числа и т.д., чтобы хорошо всё проверить.
В JavaScript мы можем использовать Math.random() . Но если что-то пойдёт не так, то нам нужно будет перезапустить тест, используя те же самые данные.
Для этого используются так называемые «сеяные псевдослучайные генераторы». Они получают «зерно», как первое значение, и затем генерируют следующее, используя формулу. Так что одно и то же зерно даёт одинаковую последовательность, и, следовательно, весь поток легко воспроизводим. Нам нужно только запомнить зерно, чтобы воспроизвести последовательность.
Пример такой формулы, которая генерирует более-менее равномерно распределённые значения:
next = previous * 16807 % 2147483647
Если мы используем 1 как зерно, то значения будут:
- 16807
- 282475249
- 1622650073
- …и так далее…
Задачей является создать функцию-генератор pseudoRandom(seed) , которая получает seed и создаёт генератор с указанной формулой.
let generator = pseudoRandom(1); alert(generator.next().value); // 16807 alert(generator.next().value); // 282475249 alert(generator.next().value); // 1622650073
yield*
**Выражение yield* **используется для того, чтобы «передать управление» функцией-генератором другому генератору или итерируемому объекту.
Синтаксис
yield* [[expression]];
Описание
Выражение yield* в функции-генераторе принимает итерируемый объект и возвращает его значения по очереди, как если бы эта функция-генератор возвращала их сама.
Значение выражения yield* само по себе равно последнему значению итерируемого объекта (т.е., того когда done равно true).
Примеры
Передача другому генератору
В следующем примере, значения полученные из g1() возвращаются из g2 вызовами next , как будто бы она вычислила их сама.
function* g1() yield 2; yield 3; yield 4; > function* g2() yield 1; yield* g1(); yield 5; > var iterator = g2(); console.log(iterator.next()); // console.log(iterator.next()); // console.log(iterator.next()); // console.log(iterator.next()); // console.log(iterator.next()); // console.log(iterator.next()); //
Другие итерируемые объекты
Помимо объектов генераторов, yield* может перебирать другие виды итерируемых объектов, т.е. массивы, строки, объекты аргументов и др.
function* g3() yield* [1, 2]; yield* "34"; yield* Array.from(arguments); // Определение этого итератора ниже yield* new PowesOfTwo(4); > var iterator = g3(5, 6); // Значения из массива console.log(iterator.next()); // console.log(iterator.next()); // // Из строки console.log(iterator.next()); // console.log(iterator.next()); // // Из аргументов console.log(iterator.next()); // console.log(iterator.next()); // // Из специального итератора console.log(iterator.next()); // console.log(iterator.next()); // console.log(iterator.next()); // console.log(iterator.next()); // // Итератор, который возвращает все степени двойки // до maximum включительно class PowersOfTwo constructor(maximum) this.maximum = maximum; this.value = 1; > [Symbol.iterator]() const self = this; return next() if (self.value > self.maximum) return done: true >; const value = self.value; self.value *= 2; return done: false, value >; >, >; > >
Собственное значение выражения yield*
yield* — это выражение, а не оператор, поэтому оно имеет значение, равное последнему значению итератора
function* g4() yield* [1, 2, 3]; return "foo"; > var result; function* g5() result = yield* g4(); > var iterator = g5(); console.log(iterator.next()); // console.log(iterator.next()); // console.log(iterator.next()); // console.log(iterator.next()); // < value: undefined, done: true >, // g4() в этой точке вернёт console.log(result); // "foo"
Спецификации
| Specification |
|---|
| ECMAScript Language Specification # sec-generator-function-definitions-runtime-semantics-evaluation |
Совместимость с браузерами
BCD tables only load in the browser
Специфичные для Firefox примечания
- Начиная с Gecko 33, разбор выражений yield было приведено к соответствию с последними спецификациями ES6 (Firefox bug 981599):
- Реализована корректная обработка разрыва строки. Разрыва строки между «yield» и «*» быть не может. Такой код вызовет SyntaxError :
function* foo() yield *[]; >
See also
- The Iterator protocol
- function*
- function* expression
- yield
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
This page was last modified on 6 янв. 2024 г. by MDN contributors.
Your blueprint for a better internet.
MDN
Support
- Product help
- Report an issue
Our communities
Developers
- Web Technologies
- Learn Web Development
- MDN Plus
- Hacks Blog
- Website Privacy Notice
- Cookies
- Legal
- Community Participation Guidelines
Visit Mozilla Corporation’s not-for-profit parent, the Mozilla Foundation.
Portions of this content are ©1998– 2024 by individual mozilla.org contributors. Content available under a Creative Commons license.Итераторы и генераторы — JS: Синхронная асинхронность
Этот урок посвящен механизму, основное предназначение которого не связано с асинхронным кодом. Этот механизм называется генераторами и, фактически, представляет из себя улучшенный итератор. А поскольку с итераторами мы тоже не знакомы, то начнем наше повествование с них.
Итак, вообразим следующую задачу: необходимо сделать объект, содержащий в себе коллекцию (чего угодно), итерируемым. На практике это означает, что мы можем идти циклом for. of по самому объекту, хотя, как мы знаем, по умолчанию это невозможно. Пример:
// Упадет с ошибкой for (const v of a: 1, b: 2 >) console.log(v); > // obj это объект и for спокойно по нему пройдется const obj = makeIterator(); // Устройство функции makeIterator будет раскрыто позже for (const v of obj) console.log(v); >Во втором примере, obj представляет из себя итерируемый объект ( iterable object ). Мы можем самостоятельно сделать его таким:
const obj = collection: ['yo', 'ya'], [Symbol.iterator]: makeIterator, >; for (const v of obj) console.log(v); > // yo // yaНапомню, что Symbol – это специальный неизменяемый тип данных. В основном используется в свойствах объектов. js предоставляет несколько встроенных символов, одним из которых и является iterator .
Чтобы сделать любой объект итерируемым, нужно создать свойство Symbol.iterator и записать туда специальную функцию, о структуре которой мы сейчас и поговорим.
Эта функция не является общей для всех итерируемых объектов, ее содержимое зависит от объектов, для которых она предназначена. Общее правило — функция должна реализовывать The iterable protocol .
const makeIterator = function () let nextIndex = 0; const next = () => if (nextIndex this.collection.length) const value = this.collection[nextIndex++]; return value, done: false >; > return done: true >; >; return next >; >;Функция makeIterator не имеет параметров, потому что так она вызывается внутри js . Из этого следует, что доступ к текущему объекту, к которому она прикреплена, возможен только через this , а значит она должна быть объявлена как functionDeclaration , а не arrowFunction . Требование к возвращаемому значению этой функции следующее:
Необходимо вернуть объект с методом next . Каждый вызов next будет возвращать объект с двумя свойствами: value и done . Свойство value – это значение текущего элемента коллекции, а done – это флаг конца коллекции. Как только next завершает перебор, то возвращается < done: true >, и это является сигналом, что итерирование завершено.
Можно продемонстрировать работу этой функции, немного переписав ее для возможности прямого вызова:
const makeIterator = coll => let nextIndex = 0; const next = () => if (nextIndex coll.length) const value = coll[nextIndex++]; return value, done: false >; > return done: true >; >; return next >; >Еще раз отмечу, что выше мы создали пример только для демонстрации. В реальном коде такая функция не сделает объект итерируемым.
const it = makeIterator(['yo', 'ya']); it.next(); // it.next(); // it.next(); //Именно так будет вызываться next , скрыто от наших глаз в момент итерации по объекту.
Что можно заметить, глядя на эту функцию? Она содержит в себе скрытое состояние, которое необходимо для запоминания текущей позиции. Как мы помним, состояние – штука сложная, и программируя в таком стиле, легко допустить ошибку.
Оказывается, что можно переложить задачу по управлению состоянием на машину. Делается это с помощью так называемых генераторов.
const makeIterator = function* (coll) for (const value of coll) yield value; > >; const it = makeIterator(['yo', 'ya']); it.next(); // it.next(); // it.next(); //Как видно из примера, генераторы вводят новый синтаксис в язык. Во-первых, это звездочка после слова function . Она просто указывает на то, что мы имеем дело с генератором. Во-вторых, выражение yield (подчеркиваю: это – не инструкция).
Генератор в отличие от обычной функции при своем вызове не выполняет тело, а возвращает специальный объект с методом next . Каждый раз, когда вызывается next , запускается тело генератора с того места, где оно остановилось последний раз. При первом вызове выполнение идет с самого начала генератора и продолжается до встречи с выражением yield . В этот момент управление передается наружу, next возвращает то, что было передано в yield , а генератор замирает в этом состоянии, на выражении yield . Последующие вызовы начинают работу от yield .
Еще один пример для осознания:
const gen = function* () yield 1; yield 2; yield 3; >; const it = gen(); it.next(); // it.next(); // it.next(); // it.next(); //const it = gen(); [. it]; // [1, 2, 3]Кроме yield в генераторах можно использовать версию yield* , которая ожидает на вход коллекцию и делает yield для каждого элемента этой коллекции.
const makeIterator = function* () yield* this.collection; >;Теперь можно переписать наш первый пример вот таким образом:
const obj = collection: ['yo', 'ya'], [Symbol.iterator]: function* () yield* this.collection; >, >; for (const v of obj) console.log(v); >Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях: