Generator functions are basically functions that you can pause at some point. You can define a generator function with function*
:
function* Generator() {
// code goes here
}
I like to named my generator functions with a capital, for reasons that will become apparent later, but generally they are named with a lowercase letter.
Then you can use the yield
keyword inside the function
function* Generator() {
yield 10;
yield 20;
}
yield
is like return
in that it pauses the function, but the difference is that the code can resume to the function later.
Now that you defined a generator function, you can make a generator out of it:
const gen = Generator();
This is also the reason I like to name generator functions with a capital. It's like when you make an instance of the class, except it's a generator function, not a class.
Calling next()
on the generator will execute the code in the function until the next yield
.
console.log(gen.next());
// Output: { value: 10, done: false }
Notice that it doesn't return the yielded value directed, but in an object. It has the yielded value, and whether the function is done.
Now if you call next()
again, it will yield 20:
console.log(gen.next());
// Output: { value: 20, done: false }
Now if you call it again, the value will be undefined but the done property will be true because the generator function is done:
console.log(gen.next());
// Output: { value: undefined, done: true }
Infinite loops are ok in generator functions, but only if you have a yield
in them.
function* Generator() {
let num = 0;
while (true) {
num++;
yield num;
}
}
This is because it doesn't run the whole code at once. It first sets num
to 0. Then it enters the loop, it increments num
, and then it yield
s it and stops.
Ok, this is the reason I think generator functions are cool, they're very easy to visualize algorithms with, especially recursive ones. This is because of yield*
.
yield*
allows you to automatically yield
everything from a different generator (or the same one if you're feeling recursive).
The reason I think this is useful is the following: say you're visualizing an algorithm like quicksort. It is really difficult to implement in a normal loop, because it's fundamentally not an iterative algorithm.
With a generator function, on the other hand, it's just so much easier.
function* Quicksort(lo, hi) {
yield* Partition(lo, hi); // Partition() can set a mid variable which is where the pivot is
yield* Quicksort(lo, mid-1);
yield* Quicksort(mid+1, hi);
}
Now you can step through every step of the quicksort algorirthm by just using next()
.