JavaScript Generators and Iterators

Produce values lazily with iterators and pausable generator functions

Overview

An iterator is any object with a next() method that returns { value, done }, and it becomes iterable by implementing Symbol.iterator. Generators (function*) are a concise way to build iterators, pausing at each yield and resuming on the next call. Together they enable lazy, on-demand sequences that can even be infinite.

Syntax / Usage

Calling a generator returns an iterator without running the body. Each next() advances to the following yield; yield* delegates to another iterable.

function* range(start, end, step = 1) {
  for (let i = start; i < end; i += step) {
    yield i
  }
}

const it = range(0, 5)
console.log(it.next()) // { value: 0, done: false }
console.log([...range(0, 5)]) // [0, 1, 2, 3, 4]

function* combined() {
  yield* range(0, 2)
  yield* ['a', 'b']
}
console.log([...combined()]) // [0, 1, 'a', 'b']

Examples

Implement a custom iterable by defining Symbol.iterator:

const evens = {
  *[Symbol.iterator]() {
    let n = 0
    while (true) yield (n += 2) // infinite sequence
  },
}

const iter = evens[Symbol.iterator]()
console.log(iter.next().value) // 2
console.log(iter.next().value) // 4

Generators can receive values back through next(value):

function* accumulator() {
  let total = 0
  while (true) {
    const amount = yield total // pauses, resumes with the passed value
    total += amount
  }
}

const acc = accumulator()
acc.next()       // prime the generator
console.log(acc.next(10).value) // 10
console.log(acc.next(5).value)  // 15

Take only the first N items from an infinite generator:

function take(iterable, count) {
  const out = []
  for (const value of iterable) {
    if (out.length >= count) break
    out.push(value)
  }
  return out
}

function* naturals() {
  let n = 1
  while (true) yield n++
}

console.log(take(naturals(), 3)) // [1, 2, 3]

Common Mistakes

  • Spreading an infinite generator with [...gen], which never terminates
  • Forgetting the first next() call is needed to reach the first yield when passing values in
  • Assuming a generator can be restarted—once exhausted, it stays done
  • Confusing yield with return; a return inside a generator sets the final done value
  • Expecting for...of to work on a plain object that lacks Symbol.iterator

See Also

functions array-methods javascript-event-loop