Artem Gorev

Мой дорогой дневничок


В 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

На этом все. Генераторы встречаются редко, но иногда они помогают создать элегантное решение, так что советую уделить им должное время.