Back to Blog
JavaScriptGeneratorsIteratorsES6

JavaScript Generator Functions Guide

Master JavaScript generators for lazy evaluation, iterators, and async control flow.

B
Bootspring Team
Engineering
November 16, 2018
7 min read

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.

Share this article

Help spread the word about Bootspring