JavaScript iterators and generators implement the iterator protocol: an iterator exposes next() returning { value, done }, while an iterable exposes Symbol.iterator so for...of and spread can pull a fresh iterator. A generator (function*) pauses with yield and resumes on the next next() call—handy for lazy sequences and readable control flow, and it pairs naturally with async patterns you may already use via async/await. This article walks through hand-rolled iterators, generator syntax, values passed into next, bounded demos of “infinite” generators, return semantics with spread/for...of, and a small for...of loop over a generator.
Tested on: Node.js v20.18.2. A short note after each runnable snippet describes what you should see in the console.
Manual iterator (javascript iterator without function*)
const numbers = [1, 2, 3, 4];
const numbersIterator = {
index: 0,
next() {
if (this.index < numbers.length) {
return { value: numbers[this.index++], done: false };
}
return { done: true };
},
};
let result = numbersIterator.next();
while (!result.done) {
console.log(result.value);
result = numbersIterator.next();
}You should see 4 lines, in order: 1, 2, 3, 4.
Generator that yields values (function* / yield)
function* numbersGenerator() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
}
const numbers = numbersGenerator();
console.log(numbers.next().value);
console.log(numbers.next().value);
console.log(numbers.next().value);You should see 3 lines, in order: 1, 2, 3.
Generator pattern: send a value into next
The first next() must usually run up to the first yield before you can inject a value.
function* addGenerator(x) {
const y = yield;
return x + y;
}
const add = addGenerator(5);
const r1 = add.next();
const r2 = add.next(7);
console.log(r1.value, r1.done);
console.log(r2.value, r2.done);You should see 2 lines, in order: undefined false, 12 true.
“Infinite” generator (bounded demo)
function* countGenerator() {
let i = 0;
while (true) {
yield i++;
}
}
const count = countGenerator();
console.log(count.next().value);
console.log(count.next().value);
console.log(count.next().value);You should see 3 lines, in order: 0, 1, 2.
Generator over an array (yield in a loop)
function* arrayIterator(array) {
for (const value of array) {
yield value;
}
}
const iterator = arrayIterator([1, 2, 3]);
console.log(iterator.next().value);
console.log(iterator.next().value);
console.log(iterator.next().value);You should see 3 lines, in order: 1, 2, 3.
return from a generator vs spread / for...of
for...of and [...generator()] consume yielded values but discard the object returned when done is true after a return statement. Manual next() calls still see that { value, done: true }.
function* oneAndDone() {
yield 1;
return "done";
}
console.log(JSON.stringify([...oneAndDone()]));
const generator = oneAndDone();
const a = generator.next();
console.log(a.value, a.done);
const b = generator.next();
console.log(b.value, b.done);
const c = generator.next();
console.log(c.value, c.done);You should see 4 lines, in order: [1], 1 false, done true, undefined true.
for...of over a generator
Any object obeying the iterator protocol works with for...of when it is iterable (generators return iterable iterators).
function* triple() {
yield "a";
yield "b";
yield "c";
}
const out = [];
for (const v of triple()) {
out.push(v);
}
console.log(out.join(","));You should see one line logging a,b,c.
Async code and async generators
Older patterns sometimes yield Promises from normal generators and resume with next(resolvedValue); today, prefer async function* with for await...of when you iterate async sources. Those APIs are intentionally not exercised here so the synchronous examples stay deterministic.
Summary
Javascript iterator and generator topics cover two related protocols: synchronous iterators (Symbol.iterator with next() returning { value, done }) and function* generators that yield values lazily. People ask when generators beat arrays—when streams are large, expensive to compute, or should pause between steps—and when async generators matter for pulling async I/O without buffering everything.
Another FAQ is how for...of interacts with iterators: any object exposing Symbol.iterator works, including generator objects, which is why generator bodies read like straight-line code even though execution hops between yields. When async sources appear, reach for async function* plus for await...of instead of trying to manually drive next on Promises.
