Генераторные функции — это функции, которые имеют возможность приостановить выполнение на неопределенный период, когда они обнаруживают «урожайность».

Давайте сравним функции генератора со скромной функцией, о которой мы все знаем —

function add() {
    let x=1;
    let y=2;
    return x+y
}

Когда функция «добавить» выполняется, выполнение происходит построчно — сначала определяется x, определяется y и, наконец, возвращается x+y. Вы можете приостановить казнь? Что ж, возможно, вы могли бы использовать обещание, но это не полная приостановка; когда обещание разрешается, выполнение возвращается. С генераторами блок кода, вызвавший генератор, может приостанавливать или возобновлять выполнение.

function* panCakeInstructions() {
    yield “Sift flour, baking powder, salt, and sugar”;
    yield “Mix milk, egg, and butter”;
    yield “Scoop batter on pan”;
    yield “Serve hot when brown on both sides”;
}

При вызове генератора panCakeInstructions создается и возвращается итератор.

let instructions = panCakeInstructions();
instructions.next().value //Sift flour, baking powder, salt, and sugar
instructions.next().value //Mix milk, egg, and butter
instructions.next().value //Scoop batter on pan
instructions.next().value //Serve hot when brown on both sides

Здорово! Что такое итератор?

ES6, помимо введения новых собственных функций и элегантного синтаксиса, также представил «протоколы». Протоколы — это соглашения по стандартизации использования. Следуя протоколам, мы все можем счастливо сосуществовать и хорошо играть друг с другом.

Протокол итератора

Объект называется итератором, если он имеет интерфейс next(), который возвращает {value: ‹any›: done: Boolean}. Полученное значение возвращается вместе с логическим флагом «готово», чтобы указать, выдал ли генератор свое последнее значение.

Итерируемый протокол

Объект является итерируемым, если он реализует «Symbol.iterator».

Хорошо, тайм-аут! Давайте на секунду поговорим о Symbol.iterator.

Symbol.iterator, согласно mdn, — это «функция с нулевыми аргументами, которая возвращает объект, соответствующий протоколу итератора». Это хорошее объяснение, но давайте посмотрим на это в действии.

Вызвав функцию Symbol.iterator, мы смогли выполнить итерацию с объектом итератора.

Вернемся к нашей функции panCakeInstructions. С помощью генераторов мы смогли приостановить выполнение функции, шаг за шагом переходить к инструкциям, выдавая (возвращая) результаты при вызове next() вызывающей стороной.

Кроме того, поскольку функция Generator возвращает итератор, мы также можем использовать for..of, который был специально введен для этой цели — для перебора итерируемых объектов.

for (const val of panCakeInstructions()){ 
   console.log(val) 
}

До Async/Await генераторы были образом жизни людей, которые хотели писать асинхронный код синхронным способом. Co, библиотека на основе генератора Node.js, стала огромным хитом среди разработчиков.

co(function *() {
    let breeds = yield getBreeds();
    let winner = yield getWinner(breeds);
    return winner;
}).catch(function(err) {
    console.error(err);
});

Функция-оболочка co принимает функцию генератора, приостанавливает ее до тех пор, пока полученное обещание не будет разрешено, и продвигается вперед. Дополнительные примеры см. в документации co.

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

Ссылки:

https://github.com/tj/co

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols

Первоначально опубликовано на javascriptstore.com 27 февраля 2018 г.