Генераторы

Генераторы представляют особый тип функции, которые используются для генерации значений. Для определения генераторов применяется символ звездочки *, который ставится после ключевого слова 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