Master the protocols behind for...of, spread, and destructuring, then use generators to produce lazy sequences.
Open this lesson in KodokonTwo distinct protocols govern iteration. An object is iterable if it exposes a method under the Symbol.iterator key; that method must return an iterator, that is, an object with a next() method returning { value, done }. Everything that consumes sequences - for...of, the spread operator ..., destructuring, Array.from, Promise.all - goes through these protocols. You can therefore make any object compatible.
const range = {
from: 1,
to: 3,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
return current <= last
? { value: current++, done: false }
: { value: undefined, done: true };
}
};
}
};
console.log([...range]); // [1, 2, 3]Writing iterators by hand is verbose. Generators (function*) produce an iterator automatically: each yield suspends execution and preserves the function's local state. This lazy evaluation lets you model infinite sequences, as long as a consumer limits the number of values requested.
function* naturals() {
let n = 0;
while (true) yield n++;
}
function* take(iterable, count) {
let index = 0;
for (const value of iterable) {
if (index >= count) return;
yield value;
index += 1;
}
}
console.log([...take(naturals(), 4)]); // [0, 1, 2, 3]Generator iterators also expose return() and throw(). A spec detail that often goes unnoticed: when you leave a for...of loop with break, return, or an exception, the engine automatically calls iterator.return(). In a generator, that runs any pending finally blocks - the ideal place to release a resource (a file, a connection, a lock).
function* readLines() {
try {
yield "line 1";
yield "line 2";
} finally {
console.log("cleanup");
}
}
for (const line of readLines()) {
console.log(line);
break; // triggers return(), hence the finally
}
// line 1, cleanuplength property and numeric indexesSymbol.iterator method that returns an object with next()Array.prototypenext() method directlyfor...of loop with break while it is consuming a generator?throw() on the iteratorreturn(), which runs the generator's finally blocksnext() is called