В JavaScript существует специальный вид функций называемый генератор. Он представляет собой функцию, которая может в определенный программистом момент остановить свое выполнение, передать управление другому коду, а позже продолжить работать как ни в чем не бывало.
Такое незамысловатое поведение реализуется с помощью единственного оператора yield
. Когда функция-генератор встречает его, она прекращает свое выполнение.
Ключевое слово yeild
может встречаться в тексте программы неограниченное количество раз. Функция-генератор должна использовать следующее ключевое слово при определении *function
. Перейдем к примеру.
function* myFirstGenerator() {
console.log(1);
yield;
console.log(2);
return 10;
}
// запустим наш генератор
let generator = myFirstGenerator();
console.log(generator.next());
console.log(generator.next());
В результате выполнения вы увидите следующий результат:
1
{ value: undefined, done: false }
2
{ value: 10, done: true }
Основной метод для взаимодействия с функцией генератор является next()
. При вызове он выполняется до ближайшего yield
или конца функции. В результате своего выполнения он возвращает объект состоящий из двух свойств: value
, done
. value
- значение которое функция-генератор передала в yield
. done
- отвечает завершила ли свое выполнение функция-генератор.
Если функция генератор выкидывает исключение, то при следующем вызове она вернет { value: undefined, done: true }
. Это свидетельствует о том, что функция прекратила свое выполнение.
Рассмотрим другой пример:
function* gen() {
console.log(1);
let x = yield ;
console.log(‘x = ‘ + x);
}
// запустим наш генератор
let generator = gen();
console.log(generator.next());
console.log(generator.next(10));
Вывод терминала:
1
{ value: undefined, done: false }
x = 10
{ value: undefined, done: true }
Как видно из примера существует способ передавать значения в генераторы, при каждом вызове next()
, в него можно передать значение.
Позже это значение будет доступно в функции-генераторе как результат вызова генератора.
В наших примерах рассматриваются конечные функции-генераторы, но ничто не мешает написать бесконечную функцию-генератор, которая будет работать вечно, например генератор арифметической прогрессии.
function *progression() {
const step = 1;
let current = 1;
for(;;) {
yield current;
current += step;
}
}
const gen = progression();
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
На выходе мы получим всеми известную числа из нашей прогрессии.
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: 4, done: false }
{ value: 5, done: false }
Мы уже рассмотрели, как функция-генератор может выкинуть исключение. Может сложится ситуация когда вы захотите передать исключение в функцию-генератор, тогда вам придется воспользоваться методом throw()
. Рассмотрим на примере.
function *progression() {
const step = 1;
let current = 1;
for(;;) {
try {
yield current;
} catch (e) {
console.log(e);
}
current += step;
}
}
const gen = progression();
console.log(gen.next());
console.log(gen.throw(new Error(‘Exception’)));
console.log(gen.next());
Как не удивительно в данном случае все корректно обработалось внутри функции-генератора, и в терминале можно наблюдать следующее:
{ value: 1, done: false }
Error: Exception
at /home/runner/index.js:18:23
at Script.runInContext (vm.js:133:20) at Object.<anonymous> (/run_dir/interp.js:156:20)
at Module._compile (internal/modules/cjs/loader.js:778:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)
at Module.load (internal/modules/cjs/loader.js:653:32)
at tryModuleLoad (internal/modules/cjs/loader.js:593:12) at Function.Module._load (internal/modules/cjs/loader.js:585:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:831:12) at startup (internal/bootstrap/node.js:283:19)
{ value: 2, done: false }{ value: 3, done: false }
Можно использовать функции-генераторы вместе с выражением for..of
.
function *progression() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
}
for( let I of progression()) {
console.log(i);
}
В результате мы получаем
1
2
3
4
5
На этом все. Генераторы встречаются редко, но иногда они помогают создать элегантное решение, так что советую уделить им должное время.