Генераторы
Генераторы представляют особый тип функции, которые используются для генерации значений. Для определения генераторов применяется символ звездочки *, который ставится после ключевого слова function. Например, определим простейший генератор:
function* getNumber(){
yield 5;
}
let numberGenerator = getNumber();
let next = numberGenerator.next();
console.log(next);
Функция getNumber представляет генератор. Функция генератора возвращает итератор. Для получения значения из генератора применяется оператор yield. То есть фактически в данном случае генератор генерирует число 5.
Далее с помощью вызова этой функции создается объект итератора в виде переменной numberGenerator. Используя этот объект, мы можем получать из генератора значения.
Для перехода к следующему значению применяется метод next(). Если мы посмотрим на консольный вывод, то мы увидим, что данный метод возвращает следующие данные:
{value: 5, done: false}
То есть по сути возвращается объект, свойство value которого содержит собственно сгенерированное значение. А свойство done указывает, достигли ли мы конца генератора.
Теперь изменим код:
function* getNumber(){
yield 5;
}
let numberGenerator = getNumber();
let next = numberGenerator.next();
console.log(next);
next = numberGenerator.next();
console.log(next);
Здесь обращение к методу next() происходит два раза:
{value: 5, done: false}
{value: undefined, done: true}
Но функция генератора getNumber генерирует только одно значение - число 5. Поэтому при повторном вызове свойство value будет иметь значение undefined, а свойство done - true, то есть работа генератора завершена.
Генератор может создавать множество значений:
function* getNumber(){
yield 5;
yield 25;
yield 125;
}
let numberGenerator = getNumber();
console.log(numberGenerator.next());
console.log(numberGenerator.next());
console.log(numberGenerator.next());
console.log(numberGenerator.next());
Консольный вывод:
{value: 5, done: false}
{value: 25, done: false}
{value: 125, done: false}
{value: undefined, done: true}
То есть при первом вызове метода next() из итератора извлекается значение, которое идет после первого оператора yield, при втором вызове метода next() - значение после второго оператора yield и так далее.
Поскольку для получения значений применяется итератор, то мы можем использовать цикл for...of:
function* getNumber(){
yield 5;
yield 25;
yield 125;
}
let numberGenerator = getNumber();
for(let num of numberGenerator){
console.log(num);
}
Консольный вывод:
5
25
125
Генератор необязательно содержит только определение операторов yield. Он также может содержать более сложную логику.
С помощью генераторов удобно создавать бесконечные последовательности:
function* points(){
let x = 0;
let y = 0;
while(true){
yield {x:x, y:y};
x += 2;
y += 1;
}
}
let pointGenerator = points();
console.log(pointGenerator.next().value);
console.log(pointGenerator.next().value);
console.log(pointGenerator.next().value);
Консольный вывод:
{x: 0, y: 0}
{x: 2, y: 1}
{x: 4, y: 2}
Передача данных в генератор
С помощью next() можно передать в генератор данные.
function* getNumber(){
let n = yield 5;
console.log("n:", n);
let m = yield 25 * n;
console.log("m:", m);
yield 125 * m;
}
let numberGenerator = getNumber();
console.log(numberGenerator.next().value);
console.log(numberGenerator.next(2).value);
console.log(numberGenerator.next(3).value);
Консольный вывод:
5
n: 2
50
m: 3
375
При втором вызове метода next():
numberGenerator.next(2).value
Мы можем получить переданные через него данные, присвоив результат первого оператора yield:
let n = yield 5;
То есть здесь переменная n будет равна 2, так как в метод next() передается число 2.
Далее мы можем использовать это значение, например, для генерации нового значения:
let m = yield 25 * n;
Соответственно, переменная m получить значение, переданное через третий вызов метода next(), то есть число 3.
Инициализация генератора
Есть также другой способ передачи данных в генератор, когда мы передаем некоторые данные в саму функцию генератора, то есть фактически инициализируем генератор некоторыми начальными данными:
function* takeItem(arr){
for(var i=0; i < arr.length; i++){
yield arr[i];
}
}
var users = ["Tom", "Bob", "Sam", "Alice", "Kate", "Ann"];
var userGenerator = takeItem(users);
var timer = setInterval(function(){
var user = userGenerator.next();
if(user.done){
clearInterval(timer);
console.log("The End...");
} else{
console.log(user.value);
}
}, 500);
В данном случае в генератор передается массив,
который используется для генерации значений в таймере.
Output