Commissioned for this museum · after Gamma, Helm, Johnson & Vlissides, 1994
Iterator
TypeScript·1994·33 lines·875 bytes
/**
* Iterator — traverse a collection without exposing how it is stored.
*/
class Ring<T> {
constructor(private readonly items: readonly T[]) {}
// Being iterable is a contract, not a base class: implement this one
// well-known method and `for...of`, spread and destructuring all work.
*[Symbol.iterator](): Generator<T> {
for (const item of this.items) {
yield item;
}
}
// The same contract, unbounded. Nothing about a sequence requires it
// to end — only that each step produces a value when asked.
*cycle(): Generator<T> {
if (this.items.length === 0) return;
for (let i = 0; ; i = (i + 1) % this.items.length) {
yield this.items[i];
}
}
}
const seasons = new Ring(["spring", "summer", "autumn", "winter"]);
console.log([...seasons]);
let n = 0;
for (const season of seasons.cycle()) {
if (n++ === 6) break;
console.log(season);
}Curator’s note
Of the twenty-three patterns, this is the one that won so completely it stopped being a pattern. It is not a class you write any more; it is a hole in the language shaped exactly like it, and for...of falls through.
The 1994 book needed an Iterator interface, a ConcreteIterator, and an Aggregate to hand one out — three types to walk a list. Here that is one method with a symbol for a name. Implement [Symbol.iterator] and spread, destructuring, for...of, Array.from and yield* all start working at once, none of which knew anything about Ring in advance.
cycle is the part worth standing in front of. It never terminates, and that is not a bug — it is the point. The pattern separates *producing* values from *deciding how many you want*, so an infinite sequence costs no more memory than a finite one and the break in the loop below is the only thing that bounds it. A collection that eagerly returns an array cannot do this at all.
The generator also holds its own position between calls. What was once an object with an index field is now a suspended function — the state is the paused stack, and there is nowhere to store a stale cursor.