Генераторы в 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 }
- При первом вызове
next()
итератор возвращает1
в качествеvalue
. - При втором вызове
next(5)
, значение5
заменит собой выражениеyield 1
и, фактически выражение превратится вlet x = 5
. - Далее код выполняется до следующего
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
На этом все. Надеюсь данная статья помогла вам разобраться с генераторами на начальном уровне.