Generator functions allow you to define iterative algorithms with pausable execution. Here's how to use them effectively.
Basic Generator#
1// Define with function*
2function* simpleGenerator() {
3 yield 1;
4 yield 2;
5 yield 3;
6}
7
8// Create iterator
9const gen = simpleGenerator();
10
11// Get values with next()
12console.log(gen.next()); // { value: 1, done: false }
13console.log(gen.next()); // { value: 2, done: false }
14console.log(gen.next()); // { value: 3, done: false }
15console.log(gen.next()); // { value: undefined, done: true }
16
17// Use in for...of
18for (const value of simpleGenerator()) {
19 console.log(value); // 1, 2, 3
20}
21
22// Spread into array
23const values = [...simpleGenerator()]; // [1, 2, 3]Yield Expression#
1function* greetGenerator() {
2 const name = yield 'What is your name?';
3 const age = yield `Hello ${name}! How old are you?`;
4 return `${name} is ${age} years old`;
5}
6
7const gen = greetGenerator();
8
9console.log(gen.next()); // { value: 'What is your name?', done: false }
10console.log(gen.next('John')); // { value: 'Hello John! How old are you?', done: false }
11console.log(gen.next(25)); // { value: 'John is 25 years old', done: true }Infinite Sequences#
1// Infinite counter
2function* infiniteCounter(start = 0) {
3 let count = start;
4 while (true) {
5 yield count++;
6 }
7}
8
9const counter = infiniteCounter(1);
10console.log(counter.next().value); // 1
11console.log(counter.next().value); // 2
12console.log(counter.next().value); // 3
13
14// Fibonacci sequence
15function* fibonacci() {
16 let [prev, curr] = [0, 1];
17 while (true) {
18 yield curr;
19 [prev, curr] = [curr, prev + curr];
20 }
21}
22
23// Take first 10
24const fib = fibonacci();
25const first10 = Array.from({ length: 10 }, () => fib.next().value);
26// [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]Yield Delegation#
1// yield* delegates to another generator
2function* inner() {
3 yield 'a';
4 yield 'b';
5}
6
7function* outer() {
8 yield 1;
9 yield* inner();
10 yield 2;
11}
12
13console.log([...outer()]); // [1, 'a', 'b', 2]
14
15// Delegate to any iterable
16function* withArray() {
17 yield* [1, 2, 3];
18 yield* 'abc';
19 yield* new Set([4, 5, 6]);
20}
21
22console.log([...withArray()]); // [1, 2, 3, 'a', 'b', 'c', 4, 5, 6]
23
24// Recursive tree traversal
25function* traverse(node) {
26 yield node.value;
27 for (const child of node.children || []) {
28 yield* traverse(child);
29 }
30}Generator Methods#
1function* controlled() {
2 try {
3 while (true) {
4 const value = yield;
5 console.log('Received:', value);
6 }
7 } finally {
8 console.log('Generator closed');
9 }
10}
11
12const gen = controlled();
13gen.next(); // Start generator
14gen.next('Hello'); // Received: Hello
15gen.next('World'); // Received: World
16
17// return() - terminate generator
18gen.return('Done'); // Generator closed, { value: 'Done', done: true }
19
20// throw() - throw error into generator
21function* errorHandler() {
22 try {
23 yield 1;
24 yield 2;
25 } catch (error) {
26 console.log('Caught:', error.message);
27 yield 'recovered';
28 }
29}
30
31const eh = errorHandler();
32console.log(eh.next()); // { value: 1, done: false }
33console.log(eh.throw(new Error('Oops'))); // Caught: Oops, { value: 'recovered', done: false }Lazy Evaluation#
1// Process large datasets lazily
2function* map(iterable, fn) {
3 for (const item of iterable) {
4 yield fn(item);
5 }
6}
7
8function* filter(iterable, predicate) {
9 for (const item of iterable) {
10 if (predicate(item)) {
11 yield item;
12 }
13 }
14}
15
16function* take(iterable, count) {
17 let taken = 0;
18 for (const item of iterable) {
19 if (taken >= count) return;
20 yield item;
21 taken++;
22 }
23}
24
25// Chain operations lazily
26function* range(start, end) {
27 for (let i = start; i < end; i++) {
28 yield i;
29 }
30}
31
32// Only processes 5 items, not all 1000000
33const result = take(
34 filter(
35 map(range(0, 1000000), x => x * 2),
36 x => x % 3 === 0
37 ),
38 5
39);
40
41console.log([...result]); // [0, 6, 12, 18, 24]Async Generators#
1// async function* for async iteration
2async function* asyncRange(start, end, delay) {
3 for (let i = start; i < end; i++) {
4 await new Promise(resolve => setTimeout(resolve, delay));
5 yield i;
6 }
7}
8
9// Use with for await...of
10async function main() {
11 for await (const num of asyncRange(1, 5, 100)) {
12 console.log(num); // 1, 2, 3, 4 (with delays)
13 }
14}
15
16// Async data fetching
17async function* fetchPages(baseUrl) {
18 let page = 1;
19 while (true) {
20 const response = await fetch(`${baseUrl}?page=${page}`);
21 const data = await response.json();
22
23 if (data.items.length === 0) return;
24
25 yield* data.items;
26 page++;
27 }
28}
29
30// Process paginated API
31async function getAllItems() {
32 const items = [];
33 for await (const item of fetchPages('/api/items')) {
34 items.push(item);
35 }
36 return items;
37}State Machine#
1function* trafficLight() {
2 while (true) {
3 yield 'green';
4 yield 'yellow';
5 yield 'red';
6 }
7}
8
9const light = trafficLight();
10console.log(light.next().value); // green
11console.log(light.next().value); // yellow
12console.log(light.next().value); // red
13console.log(light.next().value); // green (cycles)
14
15// More complex state machine
16function* connectionStateMachine() {
17 while (true) {
18 console.log('Disconnected');
19 yield 'disconnected';
20
21 console.log('Connecting...');
22 yield 'connecting';
23
24 console.log('Connected');
25 const action = yield 'connected';
26
27 if (action === 'disconnect') {
28 console.log('Disconnecting...');
29 yield 'disconnecting';
30 } else if (action === 'error') {
31 console.log('Connection error');
32 yield 'error';
33 }
34 }
35}Coroutines Pattern#
1// Simple coroutine runner
2function runCoroutine(generatorFn) {
3 const generator = generatorFn();
4
5 function handle(result) {
6 if (result.done) return Promise.resolve(result.value);
7
8 return Promise.resolve(result.value)
9 .then(res => handle(generator.next(res)))
10 .catch(err => handle(generator.throw(err)));
11 }
12
13 return handle(generator.next());
14}
15
16// Usage
17function* fetchUserData(userId) {
18 const user = yield fetch(`/api/users/${userId}`).then(r => r.json());
19 const posts = yield fetch(`/api/users/${userId}/posts`).then(r => r.json());
20 return { user, posts };
21}
22
23runCoroutine(() => fetchUserData(1)).then(data => {
24 console.log(data);
25});Iterator Protocol#
1// Make objects iterable with generators
2const range = {
3 start: 1,
4 end: 5,
5
6 *[Symbol.iterator]() {
7 for (let i = this.start; i <= this.end; i++) {
8 yield i;
9 }
10 }
11};
12
13console.log([...range]); // [1, 2, 3, 4, 5]
14
15// Class with generator
16class Collection {
17 constructor(items = []) {
18 this.items = items;
19 }
20
21 *[Symbol.iterator]() {
22 yield* this.items;
23 }
24
25 *reversed() {
26 for (let i = this.items.length - 1; i >= 0; i--) {
27 yield this.items[i];
28 }
29 }
30
31 *filtered(predicate) {
32 for (const item of this.items) {
33 if (predicate(item)) yield item;
34 }
35 }
36}
37
38const col = new Collection([1, 2, 3, 4, 5]);
39console.log([...col]); // [1, 2, 3, 4, 5]
40console.log([...col.reversed()]); // [5, 4, 3, 2, 1]
41console.log([...col.filtered(x => x % 2 === 0)]); // [2, 4]Pipeline Processing#
1// Create processing pipeline
2function pipe(...generators) {
3 return function* (input) {
4 let result = input;
5 for (const gen of generators) {
6 result = gen(result);
7 }
8 yield* result;
9 };
10}
11
12function* double(iterable) {
13 for (const x of iterable) {
14 yield x * 2;
15 }
16}
17
18function* addOne(iterable) {
19 for (const x of iterable) {
20 yield x + 1;
21 }
22}
23
24function* filterEven(iterable) {
25 for (const x of iterable) {
26 if (x % 2 === 0) yield x;
27 }
28}
29
30const pipeline = pipe(double, addOne, filterEven);
31console.log([...pipeline([1, 2, 3, 4, 5])]); // [4, 6, 8, 10]Best Practices#
Use Cases:
✓ Lazy evaluation
✓ Infinite sequences
✓ Custom iterators
✓ State machines
Patterns:
✓ yield* for delegation
✓ for...of for consumption
✓ try/finally for cleanup
✓ Combine with async/await
Performance:
✓ Memory efficient iteration
✓ On-demand computation
✓ Process streams
✓ Handle large datasets
Avoid:
✗ When simple arrays suffice
✗ Overcomplicating logic
✗ Ignoring done state
✗ Memory leaks in infinite generators
Conclusion#
Generator functions provide powerful control over iteration and execution flow. Use them for lazy evaluation of large datasets, creating custom iterators, implementing state machines, and managing async workflows. Combined with async/await as async generators, they enable elegant handling of streaming data and paginated APIs.