Generators create iterable sequences with lazy evaluation. Here's how to use them effectively.
Basic Generators#
1// Generator function
2function* countUp() {
3 yield 1;
4 yield 2;
5 yield 3;
6}
7
8const counter = countUp();
9console.log(counter.next()); // { value: 1, done: false }
10console.log(counter.next()); // { value: 2, done: false }
11console.log(counter.next()); // { value: 3, done: false }
12console.log(counter.next()); // { value: undefined, done: true }
13
14// Iterate with for...of
15for (const num of countUp()) {
16 console.log(num); // 1, 2, 3
17}
18
19// Spread into array
20const numbers = [...countUp()]; // [1, 2, 3]Infinite Sequences#
1// Infinite counter
2function* infiniteCounter(start = 0) {
3 let count = start;
4 while (true) {
5 yield count++;
6 }
7}
8
9// Take first 5
10const counter = infiniteCounter();
11for (let i = 0; i < 5; i++) {
12 console.log(counter.next().value); // 0, 1, 2, 3, 4
13}
14
15// Fibonacci sequence
16function* fibonacci() {
17 let [prev, curr] = [0, 1];
18 while (true) {
19 yield curr;
20 [prev, curr] = [curr, prev + curr];
21 }
22}
23
24// Get first 10 Fibonacci numbers
25function take(generator, n) {
26 const result = [];
27 for (let i = 0; i < n; i++) {
28 const { value, done } = generator.next();
29 if (done) break;
30 result.push(value);
31 }
32 return result;
33}
34
35console.log(take(fibonacci(), 10));
36// [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]Passing Values to Generators#
1// next() can pass values back
2function* conversation() {
3 const name = yield 'What is your name?';
4 const age = yield `Hello ${name}! How old are you?`;
5 return `${name} is ${age} years old`;
6}
7
8const chat = conversation();
9console.log(chat.next().value); // 'What is your name?'
10console.log(chat.next('John').value); // 'Hello John! How old are you?'
11console.log(chat.next(30).value); // 'John is 30 years old'
12
13// State machine
14function* trafficLight() {
15 while (true) {
16 yield 'red';
17 yield 'green';
18 yield 'yellow';
19 }
20}
21
22const light = trafficLight();
23console.log(light.next().value); // 'red'
24console.log(light.next().value); // 'green'
25console.log(light.next().value); // 'yellow'
26console.log(light.next().value); // 'red'Generator Delegation#
1// yield* delegates to another generator
2function* innerGenerator() {
3 yield 'a';
4 yield 'b';
5}
6
7function* outerGenerator() {
8 yield 1;
9 yield* innerGenerator();
10 yield 2;
11}
12
13console.log([...outerGenerator()]); // [1, 'a', 'b', 2]
14
15// Recursive tree traversal
16function* traverse(node) {
17 yield node.value;
18 for (const child of node.children || []) {
19 yield* traverse(child);
20 }
21}
22
23const tree = {
24 value: 1,
25 children: [
26 { value: 2, children: [{ value: 4 }, { value: 5 }] },
27 { value: 3, children: [{ value: 6 }] },
28 ],
29};
30
31console.log([...traverse(tree)]); // [1, 2, 4, 5, 3, 6]Custom Iterables#
1// Make object iterable
2const range = {
3 start: 1,
4 end: 5,
5
6 [Symbol.iterator]() {
7 let current = this.start;
8 const end = this.end;
9
10 return {
11 next() {
12 if (current <= end) {
13 return { value: current++, done: false };
14 }
15 return { done: true };
16 },
17 };
18 },
19};
20
21for (const num of range) {
22 console.log(num); // 1, 2, 3, 4, 5
23}
24
25// Using generator syntax
26const rangeGenerator = {
27 start: 1,
28 end: 5,
29
30 *[Symbol.iterator]() {
31 for (let i = this.start; i <= this.end; i++) {
32 yield i;
33 }
34 },
35};
36
37console.log([...rangeGenerator]); // [1, 2, 3, 4, 5]Async Generators#
1// Async generator
2async function* fetchPages(url) {
3 let page = 1;
4 let hasMore = true;
5
6 while (hasMore) {
7 const response = await fetch(`${url}?page=${page}`);
8 const data = await response.json();
9
10 yield data.items;
11
12 hasMore = data.hasMore;
13 page++;
14 }
15}
16
17// Iterate with for await...of
18async function processAllPages() {
19 for await (const items of fetchPages('/api/items')) {
20 for (const item of items) {
21 console.log(item);
22 }
23 }
24}
25
26// Stream processing
27async function* streamLines(url) {
28 const response = await fetch(url);
29 const reader = response.body.getReader();
30 const decoder = new TextDecoder();
31 let buffer = '';
32
33 while (true) {
34 const { done, value } = await reader.read();
35
36 if (done) {
37 if (buffer) yield buffer;
38 break;
39 }
40
41 buffer += decoder.decode(value, { stream: true });
42 const lines = buffer.split('\n');
43 buffer = lines.pop();
44
45 for (const line of lines) {
46 yield line;
47 }
48 }
49}Generator Utilities#
1// Map generator
2function* map(iterable, fn) {
3 for (const item of iterable) {
4 yield fn(item);
5 }
6}
7
8// Filter generator
9function* filter(iterable, predicate) {
10 for (const item of iterable) {
11 if (predicate(item)) {
12 yield item;
13 }
14 }
15}
16
17// Take first n items
18function* take(iterable, n) {
19 let count = 0;
20 for (const item of iterable) {
21 if (count >= n) break;
22 yield item;
23 count++;
24 }
25}
26
27// Skip first n items
28function* skip(iterable, n) {
29 let count = 0;
30 for (const item of iterable) {
31 if (count >= n) {
32 yield item;
33 }
34 count++;
35 }
36}
37
38// Chain generators
39function* chain(...iterables) {
40 for (const iterable of iterables) {
41 yield* iterable;
42 }
43}
44
45// Usage
46const numbers = function* () {
47 yield 1;
48 yield 2;
49 yield 3;
50 yield 4;
51 yield 5;
52};
53
54const result = [
55 ...take(
56 filter(
57 map(numbers(), x => x * 2),
58 x => x > 4
59 ),
60 2
61 ),
62];
63console.log(result); // [6, 8]Lazy Evaluation#
1// Lazy range
2function* range(start, end, step = 1) {
3 for (let i = start; i < end; i += step) {
4 yield i;
5 }
6}
7
8// Doesn't compute all values upfront
9const largeRange = range(0, 1000000);
10
11// Only computes needed values
12for (const num of take(filter(largeRange, x => x % 2 === 0), 5)) {
13 console.log(num); // 0, 2, 4, 6, 8
14}
15
16// Lazy file processing
17async function* readLargeFile(path) {
18 const file = await Deno.open(path);
19 const decoder = new TextDecoder();
20
21 for await (const chunk of file.readable) {
22 yield decoder.decode(chunk);
23 }
24}Generator for State Management#
1// Simple state machine
2function* gameState() {
3 let state = 'idle';
4
5 while (true) {
6 const action = yield state;
7
8 switch (state) {
9 case 'idle':
10 if (action === 'start') state = 'playing';
11 break;
12 case 'playing':
13 if (action === 'pause') state = 'paused';
14 if (action === 'end') state = 'gameover';
15 break;
16 case 'paused':
17 if (action === 'resume') state = 'playing';
18 if (action === 'end') state = 'gameover';
19 break;
20 case 'gameover':
21 if (action === 'restart') state = 'idle';
22 break;
23 }
24 }
25}
26
27const game = gameState();
28console.log(game.next().value); // 'idle'
29console.log(game.next('start').value); // 'playing'
30console.log(game.next('pause').value); // 'paused'
31console.log(game.next('resume').value); // 'playing'Error Handling#
1// throw() method
2function* generator() {
3 try {
4 yield 1;
5 yield 2;
6 } catch (e) {
7 console.log('Caught:', e.message);
8 }
9 yield 3;
10}
11
12const gen = generator();
13console.log(gen.next()); // { value: 1, done: false }
14console.log(gen.throw(new Error('Test'))); // Caught: Test, { value: 3, done: false }
15
16// return() method
17function* generator2() {
18 try {
19 yield 1;
20 yield 2;
21 } finally {
22 console.log('Cleanup');
23 }
24}
25
26const gen2 = generator2();
27console.log(gen2.next()); // { value: 1, done: false }
28console.log(gen2.return()); // Cleanup, { value: undefined, done: true }Real-World Example: Pagination#
1// Paginated API client
2class PaginatedAPI {
3 constructor(baseUrl) {
4 this.baseUrl = baseUrl;
5 }
6
7 async *fetchAll(endpoint, pageSize = 20) {
8 let page = 1;
9 let hasMore = true;
10
11 while (hasMore) {
12 const response = await fetch(
13 `${this.baseUrl}${endpoint}?page=${page}&size=${pageSize}`
14 );
15 const data = await response.json();
16
17 for (const item of data.items) {
18 yield item;
19 }
20
21 hasMore = data.page < data.totalPages;
22 page++;
23 }
24 }
25}
26
27// Usage
28const api = new PaginatedAPI('https://api.example.com');
29
30async function processAllUsers() {
31 for await (const user of api.fetchAll('/users')) {
32 console.log(user.name);
33 }
34}Best Practices#
When to use:
✓ Large or infinite sequences
✓ Lazy evaluation needed
✓ Custom iteration logic
✓ Async data streams
Performance:
✓ Use for lazy evaluation
✓ Avoid for small fixed arrays
✓ Clean up with return()
✓ Handle errors properly
Patterns:
✓ Combine with async/await
✓ Use yield* for delegation
✓ Create reusable utilities
✓ Implement Symbol.iterator
Conclusion#
Generators enable lazy evaluation and custom iteration. Use them for large datasets, infinite sequences, and async streams. Combine with utility functions like map, filter, and take for powerful data processing pipelines.