Функция генератора JS и ключевое слово yield

Функция генератора и yield - это совершенно новая функция в JavaScript, предоставляемая EcmaScript 6.

В этой статье мы рассмотрим следующие темы:

  1. Что такое функция-генератор и ключевое слово yield
  2. Базовый пример
  3. Как остановить функцию генератора и не возвращать из нее все значения
  4. Выбрасывание исключения
  5. Передача аргументов в функцию .next ()
  6. Использование комбинации yield *

Что такое функция генератора

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

function* generatorFn() {...}

В следующем примере все функции работают одинаково:

function* () {...}
function *() {...}
function* generatorFn() {...}
function *generatorFn() {...}

Тип и конструктор:

  1. Функция генератора будет иметь тот же тип, что и обычная функция.
  2. Функция-генератор будет иметь прототип ‘GeneratorFunction’.
console.log(typeof function* generatorFn() {}); // "function"
console.log((function*(){}).constructor.name); // "GeneratorFunction"

yield keyword: используется для приостановки и возобновления работы генератора.

Как это работает: в отличие от обычной функции-генератора функция возвращает несколько значений одно за другим, и при ее вызове выполняется только часть кода (до ключевого слова yield). Результатом оператора return будет объект-генератор, реализующий протоколы итераций.

Базовый пример

На мой взгляд, это всегда более понятно на живом примере, поэтому вот базовый пример:

let log = console.log;
function* gen1() {
  yield 1;
  yield 2;
  yield 3;
}
let iterator = gen1();
log(iterator.next()); // { value: 1, done: false }
log(iterator.next()); // { value: 1, done: false }
log(iterator.next()); // { value: 1, done: false }
log(iterator.next().done); // true

Как остановить функцию генератора и не возвращать из нее все значения

Чтобы остановить функцию генератора, нам нужно вызвать функцию .return (val). .return (val) принимает один аргумент, и этот аргумент будет возвращен как значение итерации.

let log = console.log;
function* gen1() {
  yield 1;
  yield 2;
  yield 3;
}
let iterator = gen1();
log(iterator.next()); // 1
log(iterator.return(5)); // 5
log(iterator.next().done); // true

Выбрасывание исключения

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

let log = console.log;
function* gen1() {
  try {
    yield 1;
  } catch(err) {
    console.log(`We got an error in ${err} try/catch block`);
  }
try {
    yield 2;
  } catch(err) {
    console.log(`We got an error in ${err} try/catch block`);
  }
}
let iterator = gen1();
log(iterator.next());
log(iterator.throw('first'));
log(iterator.throw('second').done);

/*
Result:
=======
{ value: 1, done: false }
We got an error in undefined try/catch block
{ value: 2, done: false }
We got an error in undefined try/catch block
true
*/

Передача аргументов в функцию .next ()

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

function* gen() {
  console.log('0, start');
  console.log(`1, ${yield}`);
  console.log(`2, ${yield}`);
  console.log(`3, ${yield}`);
}
let iterator = gen();
iterator.next(); // 0, start
iterator.next('apple'); // 1 apple
iterator.next('orange'); // 2 orange
iterator.next('banana'); // 3 banana

и еще один пример:

let log = console.log;
function* gen() {
  let a = yield 3;
  let b = yield a + 7;
  let c = yield b + 20;
  yield c + 1;
}
let iterator = gen();
log(iterator.next().value); // 3
log(iterator.next(12).value); // 19
log(iterator.next(11).value); // 31
log(iterator.next(15).value); // 16
log(iterator.next().done); // true

Объяснение (второй пример):

1. Мы вызываем `iterator.next (). Value`, он возвращает 3 в результате .next (). Value и будет приостановлен при первом yield, так что let a =… все еще не имеет значения

2. Мы вызываем iterator.next (12) .value, тогда сначала yield внутри функции .gen вернет 12 и будет присвоено переменной a, сразу после того, как yield a + 7 вернется на вершину 19 и будет приостановлено на втором уровне доходности, поэтому let b =… все еще не имеет значения.

3. Мы вызываем iterator.next (11) .value, тогда второй yield внутри функции .gen вернет 11 и будет назначен переменной b, сразу после того, как yield b + 20 вернется на вершину 31 и будет приостановлено на втором уровне доходности, поэтому let b =… все еще не имеет значения.

4. Мы вызываем iterator.next (15) .value, третий yield внутри функции .gen вернет 15 и будет назначен переменной c, сразу после того, как yield c + 1 вернется поверх 16.

5. Мы вызываем `iterator.next (). Done`, и он вернет true, потому что последний yield уже был подписан.

Использование комбинации yield *

Определение: выражение yield * используется для делегирования другому генератору или повторяемому объекту. Вот пример:

let log = console.log;
function* gen1() {
  yield 2;
  yield 3;
}
function* gen2() {
  yield 1;
  yield* gen1();
  yield 4;
  yield 5;
}
let iterator = gen2();
log(iterator.next().value); // 1
log(iterator.next().value); // 2
log(iterator.next().value); // 3
log(iterator.next().value); // 4
log(iterator.next().value); // 5
log(iterator.next().done); // true

Заключение

Спасибо, ребята, что прочитали. Надеюсь, вам понравилось, и вы узнали что-то новое, связанное с JavaScript. Пожалуйста, подпишитесь и нажмите кнопку «Хлопок», если вам понравилась эта статья.