Generator
Definition
A generator is a special type of function that can be paused and resumed. Generators are created using the function* syntax and use the yield keyword to produce values.
function* generator() {
yield 1
yield 2
yield 3
}
Creating generators
function* numbers() {
yield 1
yield 2
yield 3
}
const gen = numbers()
gen.next() // { value: 1, done: false }
gen.next() // { value: 2, done: false }
gen.next() // { value: 3, done: false }
gen.next() // { value: undefined, done: true }
Generator methods
next()
Advances the generator and returns the next value:
function* counter() {
let count = 0
while (true) {
yield count++
}
}
const gen = counter()
gen.next() // { value: 0, done: false }
gen.next() // { value: 1, done: false }
gen.next() // { value: 2, done: false }
return()
Terminates the generator:
function* numbers() {
yield 1
yield 2
yield 3
}
const gen = numbers()
gen.return(10) // { value: 10, done: true }
gen.next() // { value: undefined, done: true }
throw()
Throws an error into the generator:
function* numbers() {
try {
yield 1
} catch (error) {
console.error(error)
}
}
const gen = numbers()
gen.next() // { value: 1, done: false }
gen.throw(new Error('Failed')) // Error handled in generator
Iterating generators
for...of Loop
function* numbers() {
yield 1
yield 2
yield 3
}
for (const num of numbers()) {
console.log(num)
}
// 1
// 2
// 3
Spread Operator
function* numbers() {
yield 1
yield 2
yield 3
}
[...numbers()] // [1, 2, 3]
Generator functions
Yielding Values
function* generator() {
yield 1
yield 2
return 3 // Final value
}
Yielding from Another Generator
function* generator1() {
yield 1
yield 2
}
function* generator2() {
yield* generator1() // Delegates to generator1
yield 3
}
[...generator2()] // [1, 2, 3]
Passing Values to Generators
function* generator() {
const x = yield 1
const y = yield x + 2
return y
}
const gen = generator()
gen.next() // { value: 1, done: false }
gen.next(10) // { value: 12, done: false } (x = 10)
gen.next(20) // { value: 20, done: true } (y = 20)
Use cases
Infinite Sequences
function* fibonacci() {
let [prev, curr] = [0, 1]
while (true) {
yield curr
[prev, curr] = [curr, prev + curr]
}
}
const fib = fibonacci()
fib.next().value // 1
fib.next().value // 1
fib.next().value // 2
fib.next().value // 3
Lazy Evaluation
function* range(start, end) {
for (let i = start; i < end; i++) {
yield i
}
}
const numbers = range(0, 1000000) // Doesn't create array
numbers.next() // { value: 0, done: false }
State Machines
function* stateMachine() {
yield 'start'
yield 'processing'
yield 'complete'
}
const machine = stateMachine()
machine.next().value // 'start'
machine.next().value // 'processing'
machine.next().value // 'complete'
Async generators
Generators can be used with async/await:
async function* asyncGenerator() {
yield await fetch('/api/data1')
yield await fetch('/api/data2')
yield await fetch('/api/data3')
}
for await (const data of asyncGenerator()) {
console.log(data)
}
Best practices
-
Use for lazy sequences: When you need to generate values on demand.
-
Use for state machines: When you need to manage state across multiple calls.
-
Use async generators: For streaming data or handling async sequences.
-
Understand iteration: Generators are iterable and work with
for...of. -
Be aware of memory: Generators can be more memory-efficient than arrays for large sequences.