Генераторы — новый вид функций в современном JavaScript, который был добавлен в ECMAScript 6. Отличаются они от обычных функций тем, что могу приостанавливать своё выполнение, возвращать промежуточный результат и далее возобновлять его позже, в произвольный момент времени.
Для объявления генератора используется новая синтаксическая конструкция:
function* ()
, — её называют «функция-генератор».
// ECMAScript 6
function* createRequest(url) {
try {
let request = yield fetch(url);
let response = yield request.text();
return JSON.parse(response);
} catch(error) {
throw new Error(`Error: ${error.stack}`);
}
}
Основным методом генератора является next()
. При вызове он возобновляет выполнение
кода до ближайшего ключевого слова yield
. По достижении yield
выполнение
приостанавливается, а значение — возвращается во внешний код:
// ECMAScript 6
function* sequence() {
yield 'one';
yield 'two';
return 'three';
}
let generator = sequence();
let step = generator.next();
console.log(step);
// Ожидаемый результат: {value: "one", done: false}
let secondStep = generator.next();
console.log(secondStep);
// Ожидаемый результат: {value: "two", done: false}
let thirdStep = generator.next();
console.log(thirdStep);
// Ожидаемый результат: {value: "three", done: true}
Функции-генераторы имеют 2 отличия от обычных функций:
- Обычные функции начинаются с
function
, функции-генераторы начинаются сfunction*
; - Внутри функции-генератора есть ключевое слово
yield
с синтаксисом, похожим наreturn
. Отличие в том, что функция (в том числе функция-генератор) может вернуть значение только один раз, но отдать значение функция-генератор может любое количество раз. Выражениеyield
приостанавливает выполнение генератора, так что его можно позже обновить.
В этом и есть самая большая разница между обычными функциями и функциями-генераторами. Обычные функции не могут поставить себя на паузу, а функции-генераторы могут.
Вызов генератора выглядит так же как и обычной функции. Но после того, как вы вызовете
генератор, он ещё не начнёт выполняться. Вместо этого он вернёт приостановленный объект
Generator
. Вы можете считать, что объект Generator
— это вызов функции, замороженный
во времени. Если точнее, он заморожен прямо в самом начале, функции-генератора, перед первой
строчкой кода.
Каждый раз, когда вызывается метод next()
у объекта Generator
, вызов функции
«оттаивает» и выполняется, пока не достигнет следующего выражения yield
.
Вот почему в примере выше после вызовов метода next()
мы всякий раз получали новое
строковое значение. Эти значения производятся выражениями yield
в теле функции.
При последнем вызове next()
мы, наконец, достигли конца функции-генератора,
так что поле done
результата стало равно true
.
Стоит отметить, что генераторы не являются потоками выполнения. В языках с потоками
различные куски кода могут выполняться одновременно, обычно приводя к состояниям
гонки, недетерменированности и страстно желанному приросту производительности.
Генераторы вообще на это не похожи. Когда генератор выполняется, он работает в том
же потоке, что и код его вызвавший. Порядок выполнения последователен и строго
определён, и нет никакой параллельности. В отличие от системных потоков, генератор
останавливается только на тех местах, где в коде есть yield
.