Step 1 of 5 · Reading · ~4 min
Learn
Generators and Regex
Iterators and Generators
Behind for...of, spread ([...x]) and destructuring (const [a, b] = x) sits one small protocol. Learn it and you can make your own objects work with all of them. A generator is a function that writes that protocol for you and can pause in the middle. By the end of this lesson you can read and write both, and use generators to produce values lazily, one at a time, even from an infinite sequence.
The iteration protocol
An iterator is any object with a next() method that returns { value, done }. An iterable is any object with a method named [Symbol.iterator]() that returns an iterator. Arrays and strings are iterable, and so are Map and Set, which a later lesson covers.
for...of simply calls [Symbol.iterator]() once, then calls next() until done is true. You can make any class iterable the same way:
Destructuring pulls only as many values as it needs, so nothing is computed ahead of demand.
Generators: functions that pause
Put * after function and use yield. Calling a generator function does not run its body. It returns a generator object, which is both an iterator and an iterable:
Each next() runs the body until the next yield, hands that value out, and freezes with all local variables intact. A return x ends the sequence (x appears in the final { value: x, done: true }, but for...of and spread ignore it).
Lazy sequences compose
A generator can take an iterable and yield from it, so small pieces chain into a pipeline in which every value flows through the whole chain before the next is asked for:
Because values are produced on demand, a generator can describe an infinite sequence (while (true) yield ...) safely, as long as the consumer stops. The classic mistake is then asking for all of it: [...infinite()] never finishes and runs the machine out of memory. Take a limited number instead.
Stopping early, and other details
break,returnor a throw inside afor...ofcalls the iterator's optionalreturn()method, which makes a generator run itsfinallyblock:
yield* otherdelegates to another iterable:yield* "ab"yields"a"then"b".- A generator object is single use. Once finished it yields nothing more:
[...gen, ...gen]repeats nothing the second time.
Common mistakes
- Spreading or looping over an infinite generator without a stop.
- Reusing an exhausted generator and getting an empty result.
- Expecting
steps()to run the body. It only creates the generator. - Using
yieldinside a callback such asforEach(x => yield x). It is a syntax error;yieldbelongs directly to the generator function. - Checking the limit after pulling one value too many, so a lazy source does work nobody wanted.
Your exercise
Write three small generators that plug into a lazy pipeline over an infinite source, and the starter counts how many values were really pulled. The last point above is exactly what that count checks.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…