What Are JavaScript Generators?
One of the less-known, but cool JavaScript features are generators. Added in ES6, generators provide a different method for handling loops and asynchronous tasks.
You can think of generators as functions with a pause button.
They yield values on demand, unlike the normal functions that run to completion. This unique ability to suspend and resume makes generators ideal for producing sequences and managing asynchronous flows with grace.
Generators example
A generator function is defined using the function*
syntax, and it uses the yield
keyword to pause execution.
function* pokemonGenerator() {
yield "Charmander";
yield "Charmeleon";
yield "Charizard";
}
const evolution = pokemonGenerator();
console.log(evolution.next());
// { value: "Charmander", done: false }
console.log(evolution.next());
// { value: "Charmeleon", done: false }
console.log(evolution.next());
// { value: "Charizard", done: false }
console.log(evolution.next());
// { value: undefined, done: true }
In the above example, pokemonGenerator
is a generator function that yields 3 values. The evolution
object is an iterator created by invoking the generator function. Calling evolution.next()
returns an object with two properties:
value
, the yielded value,done
, a boolean indicating whether the generator has finished.
Yield keyword
yield
keyword is a two-way street. It pauses the generator and opens a channel for value injection.
function* pokemonEvolution() {
let pokemon = yield { name: "Charmander", strength: 10 };
pokemon = yield {
name: "Charmeleon",
strength: Math.round(pokemon.strength * 1.5)
};
pokemon = yield {
name: "Charizard",
strength: Math.round(pokemon.strength * 2)
};
}
const evolution = pokemonEvolution();
console.log(evolution.next());
// { value: {name: "Charmander", strength: 10}, done: false }
console.log(evolution.next({ strength: 20 }));
// { value: {name: "Charmeleon", strength: 30}, done: false }
console.log(evolution.next({ strength: 20 }));
// { value: {name: "Charizard", strength: 100}, done: false }
Each evolution.next()
awakens the evolution of our beloved dragon, generating new Pokemon forms into evolution
. This behavior allows generators to change their internal state between heartbeats.
Here's a breakdown of what's happening:
- The first
next()
call starts the generator and yields Charmander with a default strength of 10. - The second
next({ strength: 20 })
passes in a strength of 20 for Charmander. This is used to calculate Charmeleon's strength: 20 * 1.5 = 30. - The third
next({ strength: 50 })
passes in a strength of 50 for Charmeleon. This is used to calculate Charizard's strength: 50 * 2 = 100.
When to use generators
You shouldn't force yourself to use generators, except in situations when they are needed. Some of these situations are:
Handling large data sets
function* largeDataSet(n) {
for (let i = 0; i < n; i++) {
yield heavyComputation(i);
}
}
for (const item of largeDataSet(1000000)) {
// Process each item without loading all into memory
}
Custom iterables
class Range {
constructor(start, end, step = 1) {
this.start = start;
this.end = end;
this.step = step;
}
*[Symbol.iterator]() {
for (let i = this.start; i <= this.end; i += this.step) {
yield i;
}
}
}
for (let num of new Range(1, 10, 2)) {
console.log(num);
}
Lazy evaluation of infinite sequences
function* fibonacciSequence() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacciSequence();
for (let i = 0; i < 10; i++) {
console.log(fib.next().value);
}
State machines
function* trafficLight() {
while (true) {
yield 'green';
yield 'yellow';
yield 'red';
}
}
const light = trafficLight();
console.log(light.next().value); // 'green'
console.log(light.next().value); // 'yellow'
Of course, there are more applications for generators than those above. You can think of it as a Swiss Army knife with unfolded blades.
Conclusion
Generators are JavaScript's elegant dance of sequence and state. You probably won't need them often but there are situations where generators fit perfectly to your solution of a specific problem. In those situations, knowing what will fit perfectly as a final piece in your puzzle is always good.
Comments ()