Iterators and generators
| The iterator protocol | |
| function* generators | |
| Yield values | |
| Lazy pipelines |
The iterator protocol
An iterator is an object with a next method that returns a value plus a done flag. Loops call next until done becomes true, so any object with that shape can drive a loop.
Iterables add a computed method keyed by Symbol.iterator that creates a fresh iterator. Arrays, maps, sets, and strings already follow this contract out of the box.
const nums = [10, 20, 30];
const it = nums[Symbol.iterator]();
console.log(it.next());
function* generators
Writing next by hand gets tedious, so generator functions build iterators for you. A function star returns a paused sequence that resumes on each next call.
Use generators when the sequence has logic: counters, ranges, retries, or paged reads. The function body reads top to bottom while callers consume values one by one.
function* countUp(limit: number): Generator<number> {
for (let i = 1; i <= limit; i++) {
yield i;
}
}
for (const n of countUp(3)) {
console.log(n);
}
Yield values
Yield pauses the generator and hands one value to the caller. On the next request the function resumes right after that yield with all locals intact.
Yield delegation with yield star forwards to another generator. It keeps each sequence short while still exposing one flat stream to the loop.
function* pair(): Generator<string> {
yield "north";
yield "south";
}
function* trip(): Generator<string> {
yield* pair();
yield "home";
}
Lazy pipelines
Generators shine in pipelines because each stage pulls only what it needs. Map and take helpers stay lazy, so a huge range never materializes in memory at once.
Build the pipeline from small generators, then consume with a plain for loop. The code reads as data flow while the runtime does minimal work per step.
function* take(source: Generator<number>, n: number): Generator<number> {
let seen = 0;
for (const v of source) {
if (seen >= n) break;
seen++;
yield v * 2;
}
}
Next: Mixins
Article author: Arthur Isaev