SKU // 81

JavaScript Generators

A generator is a function that can pause in the middle and resume later. Each time it pauses, it hands out a value. That makes it the easiest way to build an iterator: instead of writing next() by hand, you write an ordinary-looking function with yield.

// function* and yield

Put a star after function to make a generator. Inside, yield hands out a value and pauses:

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

const gen = countToThree();   // nothing runs yet!

console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
{value: 1, done: false} {value: 2, done: false} {value: 3, done: false} {value: undefined, done: true}
Surprise
Calling a generator function does not run its body. It returns a generator object, which is an iterator. The body only runs, one piece at a time, when you call next().

// Watching It Pause

function* steps() {
  console.log("  start");
  yield "A";
  console.log("  resumed");
  yield "B";
  console.log("  finishing");
}

const g = steps();
console.log(g.next().value);
console.log(g.next().value);
console.log(g.next().done);
start A resumed B finishing true

Each next() runs the function from where it last paused up to the next yield, then freezes it again, keeping all its local variables intact.

// Generators Work with for...of

Because a generator object is an iterator (and an iterable), every iteration tool accepts it:

function* range(from, to, step = 1) {
  for (let n = from; n <= to; n += step) {
    yield n;
  }
}

for (const n of range(1, 5)) console.log(n);
console.log([...range(0, 20, 5)]);
1 2 3 4 5 [0, 5, 10, 15, 20]

Compare that to the hand-written range object in the previous lesson: same result, a fraction of the code.

// Making an Object Iterable, the Easy Way

const playlist = {
  songs: ["Intro", "Anthem", "Outro"],
  *[Symbol.iterator]() {
    for (const s of this.songs) yield `♪ ${s}`;
  }
};

console.log([...playlist]);
["♪ Intro", "♪ Anthem", "♪ Outro"]

// Infinite Generators

A generator only does work when asked for the next value, so it can describe an endless sequence safely. You just stop asking:

function* idMaker() {
  let id = 1000;
  while (true) {
    yield id++;
  }
}

const ids = idMaker();
console.log(ids.next().value);   // 1000
console.log(ids.next().value);   // 1001
console.log(ids.next().value);   // 1002
Watch out
Never spread an infinite generator or loop over it without a break. [...idMaker()] tries to collect every value forever and freezes the page.
// Safe: take only what you need
function take(iterable, n) {
  const out = [];
  for (const x of iterable) {
    if (out.length === n) break;
    out.push(x);
  }
  return out;
}

console.log(take(idMaker(), 4));   // [1000, 1001, 1002, 1003]

Generator objects also have the iterator helpers from the previous lesson, so you can use the built-in take() instead of writing one:

console.log(idMaker().take(4).toArray());
console.log(idMaker().filter(n => n % 2 === 0).map(n => `ID-${n}`).take(3).toArray());
[1000, 1001, 1002, 1003] ["ID-1000", "ID-1002", "ID-1004"]

Put take() before toArray(). idMaker().toArray().slice(0, 4) never finishes, because toArray() tries to collect the whole endless sequence first.

// A Classic: Fibonacci

function* fibonacci() {
  let [a, b] = [0, 1];
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

console.log(take(fibonacci(), 10));
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

// return Ends the Generator

function* untilStop(words) {
  for (const w of words) {
    if (w === "STOP") return;
    yield w;
  }
}

console.log([...untilStop(["go", "on", "STOP", "never"])]);   // ["go", "on"]

// return with a Value

A generator can return a value too. next() hands it out with done: true, but for...of and spread stop as soon as they see done: true, so they never use it:

function* myStream() {
  yield 1;
  yield 2;
  return 3;
}

console.log([...myStream()]);

const g = myStream();
console.log(g.next(), g.next(), g.next(), g.next());
[1, 2] {value: 1, done: false} {value: 2, done: false} {value: 3, done: true} {value: undefined, done: true}

If you want a value to be part of the sequence, yield it. Use return only to stop.

// Generator Methods

Every generator object has three methods:

next()Runs to the next yield and returns { value, done }
return(value)Finishes the generator right now, as if a return ran at the paused spot
throw(error)Throws an error at the paused spot inside the generator
function* count() {
  yield 1;
  yield 2;
  yield 3;
}

const c = count();
console.log(c.next());
console.log(c.return("bye"));
console.log(c.next());   // finished for good
{value: 1, done: false} {value: "bye", done: true} {value: undefined, done: true}

throw() is the same idea with an error: c.throw(new Error("Stop!")) crashes the generator with Uncaught Error: Stop! and finishes it. A generator can catch that error with try...catch, which you will meet in JS Errors.

Watch out: no arrow generators
The star only goes on function* declarations and expressions, and on object methods (*[Symbol.iterator]()). There is no arrow version: const f = *() => {} is SyntaxError: Unexpected token '*'.

Before async and await existed, programmers used generators to write code that waits for slow tasks (like loading data) in a top-to-bottom style. Today async/await does that job, so you will rarely need generators for it.

W3Schools error
W3Schools' second generator example ends with yeald 3;. That typo is not a keyword, so the whole script fails to load with SyntaxError: Unexpected number. It should be yield 3;.

// Quick Check

What does this log?

function* abc() {
  yield "a";
  yield "b";
  return "c";
}
console.log([...abc()]);
Show answer
["a", "b"]. The returned "c" comes with done: true, and spread stops there without using it.

What happens when this line runs: const g = countToThree();

  1. The body runs and returns 1
  2. The body runs to the end
  3. Nothing in the body runs yet; g is a paused generator object
  4. It throws, because generators need new
Show answer
C. Calling a generator function only creates the generator object. The body starts running on the first next().

Which is a valid way to get the first 5 values of an infinite generator nums()?

  1. [...nums()].slice(0, 5)
  2. nums().toArray().slice(0, 5)
  3. nums().take(5).toArray()
  4. Array.from(nums()).slice(0, 5)
Show answer
C. take(5) is lazy, so only 5 values are ever made. The other three all try to collect the endless sequence first and freeze the page.

// Try It

  1. Click Open Playground. Finish the generator so it yields every day of the week
  2. Add console.log lines between the yields and step through with next()
  3. Write evens(), an infinite generator of even numbers, and take the first 8
  4. Give an object of your own a generator *[Symbol.iterator]() and loop over it
// Video Walkthroughs
Last updated: Oct 10, 2026, 1:43 AM ET