JavaScript: ECMAScript 6 — Generator. Что такое генераторы.

Генераторы в ES6 это такие функции, которые могут остановить свое выполнение где-то в процессе, а потом продожить выполнение с той же точки.

Частичный перевод статьи «The Basics of JavaScript Generators» с сайта medium.com


Такие функции обычно не возвращают одно значение. Они могут возвращать разные значения в разное время при одинаковых вызовах. Генераторы основаны на концепции итераторов.

Жизнь до Генераторов

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

function foo() {
  console.log('f')
  console.log('o')
  console.log('o')
}

foo()

// f
// o
// o

Функция может закончить свою работу в одном из случаев:

  • компилятор достиг конца функции;
  • встретил ключевое слово return;
  • функция выбрасывает ошибку или исключение.

Генераторы: Мы не такие

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

function * fooGenerator() {
  yield 1
  yield 2
  yield 3
}

let fooG = fooGenerator()

Когда функция генератор вызывается, она возвращает итератор. Звездочка * после слова function говорит о том, что это функция генератор и что будет возвращен итератор.

Итерация по Генератору

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

let valueOne = fooG.next()
console.log(valueOne) 
// { value: 1, done: false }

let valueTwo = fooG.next()
console.log(valueTwo)
// { value: 2, done: false }

let valueThree = fooG.next()
console.log(valueThree)
// { value: 3, done: false }

let valueFour = fooG.next()
console.log(valueFour)
// { value: undefined, done: true }

Как вы можете заметить, возвращаемое значение не является тем что именно отдается с помощью yield. Вместо примитива (в нашем случае) мы имеем объект с двумя полями.

Поле value содержит генерируемое значение. Поле done является флагом, сигнализирующим о том, закончил ли генератор все итерации или, другими словами, остались ли еще в генераторе необработанные шаги.

Использование return в генераторах

Функция генератор может иметь return. Как только генератор встречает return он обновляет флаг done в true, а value становится возвращенное значение.

function * generator() {
  yield 1
  return 5
  yield 2
}

let iterator = generator()

iterator.next()
// { value: 1, done: false }

iterator.next()
// { value: 5, done: true }

iterator.next()
// { value: undefined, done: true }

Передача параметров в метод next

Как мы увидели, генераторы могут генерировать значения с помощью yield. Но генераторы также могут и принимать параметры через метод next. В коде ниже приведен пример. Рассмотрим его детальнее.

function * gen() {
  let x = yield 1
  yield x + 1
}

let iterator = gen()

iterator.next()
// { value: 1, done: false }

iterator.next(5)
// { value: 6, done: false }

iterator.next()
// { value: undefined, done: true }
  1. При первом вызове next() итератор возвращает 1 в качестве value.
  2. При втором вызове next(5), значение 5 заменит собой выражение yield 1 и, фактически выражение превратится в let x = 5.
  3. Далее код выполняется до следующего yield. В нашем случае это yield x + 1. Так как x у нас равен 5 — итератор возвращается 6 в качестве value.

Генераторы и циклы for … of

Так как генератор возвращает итератор, то можно использовать полученный итератор в циклах for ... of. Они самостоятельно обрабатывают итераторы вызывая next за нас. Например:

function * numbers() {
  yield 1
  yield 2
  yield 3
}

for (let i of numbers()) {
  console.log(i)
}

// 1 2 3

На этом все. Надеюсь данная статья помогла вам разобраться с генераторами на начальном уровне.