Использование функций генератора JavaScript для Range

Функции генератора
Генераторы JavaScript ES6 позволяют вам определять функции, из которых можно выйти, а затем снова войти, сохраняя при этом их контекст (привязки переменных). Они определяются с использованием function* (ключевое слово function, за которым следует звездочка) и используют выражения yield для возврата результата. Например:

function* generateRange(end, start = 0, step = 1) {
  let x = start - step;
  while(x < end - step) yield x += step;
}

const gen5 = generateRange(5);
let x = gen5.next();

while (!x.done) {
  console.log(x.value);
  x = gen5.next();
} // Logs: 0, 1, 2, 3, 4
В приведенном выше примере мы определяем функцию генератора generateRange, которая будет возвращать каждое значение между началом и концом, каждый раз увеличиваясь на шаг. Мы используем объект генератора для вызова Generator.prototype.next() до тех пор, пока он не вернет {value: undefined, done: true}.

Символ.iterator
Symbol.iterator указывает итератор по умолчанию для объекта. Часто Symbol.iterator реализуется с помощью функции-генератора. Пример:
const iterableXx = {
  [Symbol.iterator]: function* () {
    yield 1;
    yield 2;
  }
};

console.log([...iterableX]); // [1, 2]

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

Собираем все вместе
Зная, как работают обе концепции, мы можем объединить их для создания генератора диапазонов, аналогичного диапазонам Python или Ruby:

const range = (end, start = 0, step = 1) => {
  function* generateRange() {
    let x = start - step;
    while(x < end - step) yield x += step;
  }
  return {
    [Symbol.iterator]: generateRange
  };
}

console.log([...range(7)]); // [0, 1, 2, 3, 4, 5, 6]
for (let i of range(8, 2, 2)) console.log(i); // Logs: 2, 4, 6

Ответить