Генераторы

Последнее обновление: 20.11.2023

Генераторы представляют особый тип функции, которые используются для генерации значений. Для определения генераторов применяется символ звездочки *, который ставится после ключевого слова function. Например, определим простейший генератор:

function* getNumber(){
	yield 5;
}
const numberGenerator = getNumber();
const result = numberGenerator.next();
console.log(result);	// {value: 5, done: false}

Здесь функция getNumber() представляет генератор. Основные моменты создания и применения генератора:

  • Генератор определяется как функция с помощью оператора function* (символ звездочки после слова function)

    function* getNumber(){ .... }

    Функция генератора возвращает итератор.

  • Для возвращения значения из генератора, как и вообще в итераторах, применяется оператор yield, после которого указывается возвращаемое значение

    yield 5;

    То есть фактически в данном случае генератор getNumber() генерирует число 5.

  • Для получения значения из генератора применяется оператор применяется метод next()

    const result = numberGenerator.next();

    Так, в примере с помощью вызова функции getNumber() создается объект итератора в виде константа numberGenerator. Используя этот объект, мы можем получать из генератора значения.

И если мы посмотрим на консольный вывод, то мы увидим, что данный метод возвращает следующие данные:

{value: 5, done: false}

То есть по сути возвращается объект, свойство value которого содержит собственно сгенерированное значение. А свойство done указывает, достигли ли мы конца генератора.

Можно заметить, что генераторы похожи на итераторы, но по сути генераторы - это особая форма итераторов.

Теперь изменим код:

function* getNumber(){
	yield 5;
}
const 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;
}
const 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 и так далее.

Для упрощения мы можем возвращать в генераторе элементы из массива:

const numbers = [5, 25, 125, 625];
function* getNumber(){
	for(const n of numbers){
		yield n;
	}
}
const numberGenerator = getNumber();
console.log(numberGenerator.next().value);	// 5
console.log(numberGenerator.next().value);	// 25

При этом важно понимать, что между двумя последовательными вызовами next() может пройти некоторое неопределенное время, между ними могут располагаться какие-то другие действия, и все равно генератор будет возвращать свое следующее значение.

const numberGenerator = getNumber();
console.log(numberGenerator.next().value);		// 5
// ряд других действий

console.log(numberGenerator.next().value);		// 25

Генератор необязательно содержит только определение операторов 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}

Возвращение из генератора и функция return

Как выше мы увидели, каждый последующий вызов next() возвращает следующее значение генератора, однако мы можем завершить работу генератора с помощью метода return():

function* getNumber(){
	yield 5;
	yield 25;
	yield 125;
}
const numberGenerator = getNumber();
console.log(numberGenerator.next());	// {value: 5, done: false}
numberGenerator.return();	// завершаем работу генератора
console.log(numberGenerator.next());	// {value: undefined, done: true}

Получение значений генератора в цикле

Поскольку для получения значений применяется итератор, то мы можем использовать цикл for...of:

function* getNumber(){
	yield 5;
	yield 25;
	yield 125;
}
const numberGenerator = getNumber();

for(const num of numberGenerator){
	console.log(num);
}

Консольный вывод:

5
25
125

Также мы можем применять и другие типы циклов, например, цикл while:

function* getNumber(){
	yield 5;
	yield 25;
	yield 125;
}
const numberGenerator = getNumber();

while(!(item = numberGenerator.next()).done){
	console.log(item.value);
}

Передача данных в генератор

Инициализация генератора

Функция генератора, как и любая другая функция, может принимать параметры. Соответственно через параметры мы можем передать в генератор некоторые данные. Например:

function* getNumber(start, end, step){
	for(let n = start; n <= end; n +=step){
		yield n;
	}
}
const numberGenerator = getNumber(0, 8, 2);

for(num of numberGenerator){
	console.log(num);
}

Здесь в функцию генератора передается начальное конечное значения и шаг приращенния чисел. Консольный вывод:

0
2
4
6
8

Другой пример - определим генератор, который возвращет данные из массива:

function* generateFromArray(items){
	for(item of items) 
		yield item;
}
const people = ["Tom", "Bob", "Sam"];
const personGenerator = generateFromArray(people);
for(person of personGenerator)
	console.log(person);

В данном случае в генератор передается массив, который используется для генерации значений. Консольный вывод:

Tom
Bob
Sam

Передача данных в метод next

С помощью next() можно передать в генератор данные. Переданные в этот метод данные можно получить в функции генератора через предыдущий вызов оператора yield:

function* getNumber(){
	const n = yield 5;		// получаем значение numberGenerator.next(2).value
	console.log("n:", n);
	const m = yield 5 * n;	// получаем значение numberGenerator.next(3).value
	console.log("m:", m);
	yield 5 * m;
}
const numberGenerator = getNumber();

console.log(numberGenerator.next().value);		// 5
console.log(numberGenerator.next(2).value);		// 10
console.log(numberGenerator.next(3).value);		// 15

Консольный вывод:

5
n: 2
10
m: 3
15

При втором вызове метода next():

numberGenerator.next(2).value

Мы можем получить переданные через него данные, присвоив результат первого вызова оператора yield:

const n = yield 5;

То есть здесь константа n будет равна 2, так как в метод next() передается число 2.

Далее мы можем использовать это значение, например, для генерации нового значения:

const m = yield 5 * n;

Соответственно, константа m получит значение, переданное через третий вызов метода next(), то есть число 3.

Обработка ошибок генератора

С помощью функции throw() мы можем сгенерировать в генераторе исключение. В качестве параметра в эту функцию передается произвольное значение, которое представляет информацию об ошибке:

function* generateData(){
	try {
		yield "Tom";
		yield "Bob";
		yield "Hello Work";
	}
	catch(error) {
		console.log("Error:", error);
	}
} 
const personGenerator = generateData();
console.log(personGenerator.next());		// {value: "Tom", done: false}
personGenerator.throw("Something wrong");	// Error: Something wrong
console.log(personGenerator.next());		// {value: undefined, done: true}

Прежде всего в функции генератора для обработки возможного исключения используем конструкцию try..catch. В блоке catch с помощью параметра error мы можем получить информацию об ошибке, которая передается в функцию throw().

Далее при использовании генератора мы можем вызвать эту функцию, передавая в нее произвольную информацию об ошибке (в данном случае это просто некоторое строковое сообщения)

personGenerator.throw("Something wrong");

В итоге этот вызов приведет к генерации исключения в функции генератора, и управление перейдет к блоку catch, который выводит информацию об ошибке на консоль:

catch(error) {
	console.log("Error:", error);
}

Консольный вывод программы:

{value: "Tom", done: false}
Error: Something wrong
{value: undefined, done: true}

Стоит отметить, что после вызова функции throw() генератор завершает работу, а далее при вызове метода next() мы получим результат {value: undefined, done: true}

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850