Countless hours are poured into designing functions that run in the fraction of a second. When functions execute so quickly, their ingenious implementations are not easily appreciated. Let’s slow them down, and take the necessary time to watch them work.
In this article, I go over the basics of generator functions and how to use them to visualize a function’s process.
What's a Generator Function?
Generator functions are new to JavaScript, and many people have been struggling to find real-world practical uses for them. I’m excited to show you a cool way to use them, but let’s go over the basics first. Here’s a simple Generator function:
function * myGeneratorFunction(arg) {
yield 1;
yield arg;
}
It looks a lot like a normal function except for two differences: an asterisk (*) after function
, and the use of the yield
statement.
Below is how myGeneratorFunction is used:
const generator = myGeneratorFunction('hello world');
console.log(generator.next().value)
// logs out 1
console.log(generator.next().value)
// logs out 'hello world'
Calling a Generator Function does not execute it right away, instead it returns a Generator object. Calling .next()
on the Generator object causes myGeneratorFunction to execute up to the first yield statement, returning the value appearing after the yield keyword. Generators allow us to stop and start the execution of a function. Check out the the MDN page on Generators for more information.
Why visualize a function's process anyway?
Visualizing a function’s process helps when trying to understand the implementation, and can result in fascinating animations and impressive effects. Take this video visualizing various sorting algorithms for example:
This video illustrates why process visualization is awesome:
- Watching the process of sorting is strangely captivating.
- The differences in how each sorting algorithm works are instantly obvious.
- What better way to interest someone in how something works, than to make how it works look cool?
Let's visualize!
Computers nowadays run ridiculously, faster-than-Usain-Bolt, mind-bogglingly fast. That means that functions run just as fast. With a Generator we can slow down the function process so that it operates at 60 steps per second. At this speed we can watch a function do what it does best, in real time as it does it. It will be like watching the world’s fastest sprinter in slow motion, seeing individual muscles contract and relax in a single step.
For our example let's shamelessly copy the youtube video above and visualize the insertion sort algorithm with a bar graph. Below are two pieces of code we will need. One for the basic algorithm, and one for drawing a bar graph. After these pieces of code, we'll see how to easily put them together.
This is the basic insertion sort algorithm implementation:
function insertionSort(inputArray) {
for (let i = 0; i < inputArray.length; i++) {
const value = inputArray[i];
let j = i - 1;
while (j >= 0 && value < inputArray[j]) {
inputArray[j+1] = inputArray[j];
j -= 1;
}
inputArray[j+1] = value
}
return inputArray;
}
And below we have a function that draws an array as a bar graph on a canvas. I use the 2d Canvas API:
Now back to our regular programming. In order to slow down our insertion sort function we are going to rewrite it as a Generator function. Sounds tricky, right? It’s actually the opposite of tricky, it’s SUPER EASY. This is the rewritten insertion sort:
function * insertionSort(inputArray) {
for (let i = 0; i < inputArray.length; i++) {
const value = inputArray[i];
let j = i - 1;
while (j >= 0 && value < inputArray[j]) {
inputArray[j+1] = inputArray[j];
j -= 1;
yield inputArray;
}
inputArray[j+1] = value
}
return inputArray;
}
There are only two changes. We add an *
after the function keyword and add a yield
statement whenever we want to draw a frame in the animation, yielding the array being sorted. With those simple changes, we’ve converted a function into a Generator function that is executed one step at a time, and yields the data we need to visualize its process. This rewrite is great because it’s unintrusive - there’s almost no chance the conversion will affect the logic of the function.
Now let’s put drawGraphFromArray
and our new insertionSort
Generator function together in a requestAnimationFrame
render loop.
// code from above ...
const randomArr = Array(50).fill(0).map(Math.random);
const sortGenerator = insertionSort(randomArr);
function renderLoop() {
const yieldedArray = sortGenerator.next().value;
drawGraphFromArray(yieldedArray);
requestAnimationFrame(renderLoop);
}
renderLoop();
resulting in our finished animation:
In the final animation above, we see the bar graph go from a jagged mess to a beautiful staircase. To achieve this, we ask our insertion sort to operate at one step per render loop with .next()
. requestAnimationFrame
makes sure our render loop runs at 60 frames per second, the perfect speed for a smooth animation.
Insertion sort is a simple example of what we can do...
This article was originally published on my blog http://elliot.website/a/?Visualizing%20Process%20with%20ES6%20Generators. Check it out to see some bonus content about creative coding using generators.
Thanks for reading. What's a creative way you've used generator function?
Top comments (1)
I didn't even know there is such a thing.
Thank you for letting me know with a wonderful example!