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

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

Как работают функции-генераторы?

Генераторная функция создается путем добавления * после ключевого слова функции function*.

Его можно приостановить, используя ключевое слово yield в теле функции.

Вызов функции-генератора возвращает итератор вместо выполнения определения функции.

Метод .next() используется для перебора тела функции-генератора. Каждый раз, когда метод вызывается, он возвращает объект с двумя свойствами. Свойство value, содержащее полученное значение, и свойство done с логическим значением, которое является ложным, пока вы не достигнете оператора возврата функций.

Член: все функции имеют неявный возврат undefined

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

function* generator() {
 yield 1;
 yield 2;
 yield 3;
}

Повторить вручную:

let gen = generator();
console.log(gen.next()); // {value: 1, done: false}
console.log(gen.next()); // {value: 2, done: false}
console.log(gen.next()); // {value: 3, done: false}
console.log(gen.next()); // {value: undefined: done: true}

Программно повторить:

let loop = generator();
let current = loop.next(); // {value: 1, done: false}
while(!current.done){
 console.log(current.value);
 current = loop.next();
}
// 1
// 2
// 3

.следующий(значение)

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

function* generator() {
  let multiplyBy = yield 1;
  console.log("multiplyBy:", multiplyBy);
  yield 2 * one;
  yield 3 * one;
}
let gen = generator();
console.log(gen.next()); // {value: 1, done: false}
// multiplyBy: undefined
console.log(gen.next()); // {value: NaN, done: false}
console.log(gen.next()); // {value: NaN, done: false}
let gen = generator();
console.log(gen.next()); // {value: 1, done: false}
// multiplyBy: 1
console.log(gen.next(1)); // {value: 2, done: false}
console.log(gen.next()); // {value: 3, done: false}

Генераторы на генераторах

Функции-генераторы могут давать другие функции-генераторы. yield* означает, что будет получена другая функция генератора.

Полученный генератор будет полностью повторен перед следующим оператором yield в исходном генераторе:

function* generator2() {
 yield 3;
 yield 4;
 yield 5;
}
function* generator1() {
 yield 1;
 yield 2;
 yield* generator2();
 yield 4;
}
const gen = generator1()
console.log(gen.next().value) // 1
console.log(gen.next().value) // 2
console.log(gen.next().value) // 3
console.log(gen.next().value) // 5
console.log(gen.next().value) // 6
console.log(gen.next().value) // 4
* Visualized *
generator1 | generator2
1 |
2 |
  | 3
  | 6
  | 5
4 |

Зачем использовать функции-генераторы?

Функции-генераторы в сочетании с обещаниями могут обеспечить простой способ управления асинхронным поведением.

Функции асинхронного генератора по сути такие же, как асинхронные/ожидающие функции.

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

Настройка асинхронного поведения для функции генератора требует больше усилий, чем использование функции async/await. В зависимости от вашего варианта использования использование функций async/await может обеспечить более простой способ обработки асинхронного поведения.

Добавление асинхронного поведения:

function async(generatorDefinition) {
  return function () {
    let generator = generatorDefinition.apply(this, arguments);
    
    function handle(result) {
      if (result.done) return Promise.resolve(result.value); 
      return Promise.resolve(result.value).then(function (response){
          return handle(generator.next(response));
        },
        function (err) {
          return handle(generator.throw(err));
        })
    };
    try {
      return handle(generator.next());
    } catch (ex) {
      return Promise.reject(ex);
    };
  };
};

Эта функция более высокого порядка позволяет нам расширить асинхронную функциональность наших генераторов.

Функция handle получает возвращенный объект fromgenerator.next(), а затем рекурсивно запускается после разрешения каждого промиса. Это продолжается до тех пор, пока не завершится итерация генераторов.

response из обещания передается в метод .next(value), чтобы переменные в теле функции-генератора имели правильно присвоенные значения.

Асинхронный генератор в действии:

const getUserId = new Promise((resolve) => {
  setTimeout(() => resolve("12345"), 1000);
});
const getFollowersById = (id) => new Promise((resolve) => {
 setTimeout(() => resolve({ data: [] }), 1000);
});
const setFollowers = (id, followers) => {
  console.log("user id set:", id);
  console.log("followers:", followers);
};
const getFollowers = function* () {
  let userId = yield getUserId;
  let followers = yield getFollowersById(userId);
  setFollowers(userId, followers.data);
};
const gen = async(getFollowers);
gen();
// user id: 12345
// followers: []

Спасибо за чтение!

Источники:

🙌🏽 ❤️️🙏🏽