Skip to content
Laviyx Learn

Try

JavaScript course menu

JavaScript Iterators and Generators: Symbol.iterator, function* and yield

Learn the JavaScript iteration protocol: iterables and iterators, Symbol.iterator, making your own objects work with for...of and spread, generator functions with function* and yield, lazy and infinite sequences, and async iteration with for await...of.

Advanced9 min readUpdated
On this page

for...of, spread (...), destructuring and Array.from all work on arrays, strings, Maps and Sets. They work because those types follow the iteration protocol. You can make your own objects follow it too, most easily with generators.

Iterables and iterators#

  • An iterable is an object with a [Symbol.iterator]() method that returns an iterator.
  • An iterator is an object with a next() method that returns { value, done } each time it is called.

You can drive an iterator by hand to see how for...of works:

JavaScript
const letters = ["a", "b"];
const iterator = letters[Symbol.iterator]();

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
Output
{ value: 'a', done: false }
{ value: 'b', done: false }
{ value: undefined, done: true }

for...of simply calls next() until done is true.

Making an object iterable#

JavaScript
const range = {
  from: 1,
  to: 4,
  [Symbol.iterator]() {
    let current = this.from;
    const last = this.to;
    return {
      next() {
        return current <= last ? { value: current++, done: false } : { value: undefined, done: true };
      },
    };
  },
};

for (const n of range) console.log(n);
console.log([...range]);
console.log(Math.max(...range));
Output
1
2
3
4
[ 1, 2, 3, 4 ]
4

Now range works everywhere an iterable is expected. Writing iterators by hand is verbose, though; generators make it much shorter.

Generators#

A generator function is written with function*. Calling it returns a generator (an iterator) without running the body. Each next() runs the body until the next yield, which hands out a value and pauses:

JavaScript
function* countdown(from) {
  console.log("(starting)");
  while (from > 0) {
    yield from;
    from--;
  }
  return "lift off";
}

const gen = countdown(3);
console.log(gen.next());
console.log(gen.next());
console.log([...gen]);
console.log(gen.next());
Output
(starting)
{ value: 3, done: false }
{ value: 2, done: false }
[ 1 ]
{ value: undefined, done: true }
  • Nothing ran until the first next(): notice when "(starting)" appears.
  • Spread used up the remaining value; the return value is not included in iteration.
  • A generator can only be iterated once.

The range object from earlier, rewritten with a generator method:

JavaScript
const range = {
  from: 1,
  to: 4,
  *[Symbol.iterator]() {
    for (let n = this.from; n <= this.to; n++) yield n;
  },
};
console.log([...range]);
Output
[ 1, 2, 3, 4 ]

Lazy and infinite sequences#

Generators produce values on demand, so a sequence can even be infinite, as long as the consumer stops:

JavaScript
function* ids(prefix) {
  let n = 1;
  while (true) yield `${prefix}-${String(n++).padStart(3, "0")}`;
}

const orderIds = ids("ORD");
console.log(orderIds.next().value);
console.log(orderIds.next().value);

function* take(iterable, count) {
  for (const item of iterable) {
    if (count-- <= 0) return;
    yield item;
  }
}

function* fibonacci() {
  let [a, b] = [0, 1];
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}
console.log([...take(fibonacci(), 10)]);
Output
ORD-001
ORD-002
[
  0, 1,  1,  2,  3,
  5, 8, 13, 21, 34
]

(Node.js prints longer arrays of numbers across several lines like this.) Nothing is computed until it is needed, which also helps when processing large data piece by piece.

yield*#

yield* delegates to another iterable, yielding each of its values:

JavaScript
function* allCourses() {
  yield* ["HTML", "CSS"];
  yield* "JS";
}
console.log([...allCourses()]);
Output
[ 'HTML', 'CSS', 'J', 'S' ]

Async iteration#

for await...of loops over async iterables, waiting for each value. Async generators (async function*) produce them, for example when reading data page by page from an API:

JavaScript
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function* fetchPages(total) {
  for (let page = 1; page <= total; page++) {
    await wait(10);   // stand-in for a network request
    yield [`item ${page}a`, `item ${page}b`];
  }
}

for await (const items of fetchPages(3)) {
  console.log(items);
}
Output
[ 'item 1a', 'item 1b' ]
[ 'item 2a', 'item 2b' ]
[ 'item 3a', 'item 3b' ]

Node.js streams, such as reading a file line by line, are async iterables too.

Practice#

Practice exercise

Write a generator evens(limit) that yields the even numbers from 0 up to and including limit, and a generator map(iterable, fn) that yields fn(item) for each item. Use them together to print the squares of the even numbers up to 10.

Show solution
JavaScript
function* evens(limit) {
  for (let n = 0; n <= limit; n += 2) yield n;
}

function* map(iterable, fn) {
  for (const item of iterable) yield fn(item);
}

console.log([...map(evens(10), (n) => n * n)]);
Output
[ 0, 4, 16, 36, 64, 100 ]

Key takeaway#

Finished this lesson?