Вопросы на собеседовании фронтенд-разработчика. Генераторы в JavaScript

Определение

Генератор — особая разновидность функций, которые могут останавливаться и запускаться в определённой точке кода (yield).

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

function* someGenerator(a, b) {
  return a + b;
}

const iterator = someGenerator(2, 3);
const result = iterator.next();
console.log(result.value) // 5

Запуск и остановка генератора

Вызов функции-генератора ещё не запускает его работу. В момент вызова происходит конструирование итератора (const iterator = someGenerator()), который будет управлять генератором.

Запуск генератора произойдёт на первом вызове next() итератора. Далее последует переход к команде yield и только после этого первый вызов next() завершится. В этот момент генератор находится в приостановленном состоянии. Со следующим вызовом next() будет осуществлён переход к следующей команде yield или в конец генератора.

Значение каждого шага генератора находятся в свойстве value объекта, который возвращает вызов next().

Передача значений

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

После запуска генератора (первый вызов next()) происходит переход к yield. Совершая второй вызов next() мы можем передать в генератор значение. Это значения будет результатом выполнения yield:

function* someGenerator(message) {
  const time = yield
  return `${message} <<< ${time}` ;
}

const iterator = someGenerator('текст');
iterator.next(); // запуск генератора и его остановка на yield

const result = iterator.next('15:45'); // результат для yield
console.log(result.value) // текст <<< 15:45

Генератор способен возвращать значения наружу. Это выглядит как обычный return функции, только возвращаемое значение попадает в свойство value. Как указывалось выше, это свойство находится в результате вызова next().

Если после yield указать какое-либо значение и вызвать next(), который запускает переход именно к этому yield, то мы сразу получим к нему доступ через свойство value. Если ничего не указывать, то в value будет undefined.

Как итог, во время работы генератора образуется двусторонняя система передачи значений между yield и next().

Цикл for..of

Для перебора итератора можно использовать цикл for..of. Он позволяет не вызывать каждый раз метод next() итератора для получения значения.

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

const iterator = someGenerator();
for(const value of iterator) {
  console.log(value);
}
// 1
// 2
// 3

Команда return(..) и try..finally

Завершить итератор можно принудительно. Для этого нужно вызвать у итератора метод return(..), в который можно передать значение для "финального" value.

const iterator = someGenerator();
const result = iterator.return('the end');

console.log(result) // { value: 'the end', done: true }

Если внутри генератора есть конструкция try..finally, то она будет выполняться даже при внешнем завершении.

Передача управления

У функции-генератора существует возможность передачи управления (делегирования). Это реализуется с помощью команды yield *. Передавать управление можно не только другой функции-генератор, но и любому итерируемому объекту.

const someArr = ['-_-', '!'];
function* someGenerator() {
  yield 'start';
  yield *someArr;
  yield 'finish';
}

for(const value of someGenerator()) {
  console.log(value);
}
// start
// -_-
// !
// finish

Когда итератор видит команду yield *anotherGenerator(), то anotherGenerator() создаст свой итератор, который после завершения вернёт управление обратно. Важно, что команда yield * передаёт управление именно итерациями, а не генератором.

function* mainGenerator() {
  yield 'start';
  yield *anotherGenerator();
  yield 'finish';
}

function* anotherGenerator() {
  yield 'some action 1';
  yield 'some action 2';
}

for(const value of mainGenerator()) {
  console.log(value);
}
// start
// some action 1
// some action 2
// finish

Передача управления упрощает чтение кода и его тестирование.

Последние обновления

© 2023 — 2026 nbeam.ru