ES6 Generators in JavaScript, a Real-World Use Case
By: Michael Smart
Tags: JavaScript, ES6

ES6 Generators in JavaScript, a Real-World Use Case

Photo by Jon Sailer on Unsplash

My problem with Generators

I don’t mind admitting that since Generators were introduced, I’ve had a hard time understanding what problems they could solve for me. While the documentation is comprehensive and useful, I was still left feeling a little unsure about their application.

I’ve recently stumbled upon what I think is a valid use case and one that might go a long way in helping others to understand what it is they can do. This is by no means a deep-dive into Generators in JavaScript. However, it might serve to supplement the documentation for those, who like me, are still searching for a context in which this new feature can be used.

I promise there are no examples involving prime numbers or the Fibonacci pattern.

A rough guide to generators

Before we look at some code, I thought I’d have a go at summarising my understanding of Generators.

From the documentation:

Generator functions allow you to define an iterative algorithm by writing a single function whose execution is not continuous

The difference here is the non-continuous execution. When we write a synchronous function in JavaScript, we typically expect that the code inside will execute until we reach a return statement, an error, or the end of the function block:

With a Generator function, we are changing that expectation with the yield keyword. When we encounter yield in our function we are expressing that we’d like the execution to pause, allowing us to get a value out of, or into, our function before it has completed its execution:

In this admittedly contrived example, we can extract the shouty value out of the function before we reach our return statement. Let’s see how we might pass a value into our paused function:

Passing a value into the next() method exposed by the Iterator Protocol allows us to use this when resuming the execution.

It is these type of examples that leave me wanting more and that have lead me to think more about the generator’s real-world application.

The typewriter problem

While working on a text adventure game engine, I needed to create a way of simulating a typing effect for a string. I wanted an implementation where I could invoke a function with a string recursively to retrieve the new typed representation of the string until it was complete.

My first approach was something along the lines of this:

This solution, for the most part, does what I need. However, there was one thing that didn’t feel quite right. Due to the continuous nature of the function, I have to expose my print function to it. Whether this is injected as an argument in the above example, or exposed in the enclosing scope or context.

With this constraint, my typing function is not only responsible for typing a string but also printing it as well. There is something about this implicit printing that doesn’t feel right, what if I want to do something else with each iteration of the string’s typed state?

If there was a way to pause the function’s execution on each iteration and extract the value, I could simply pass that value to a function that is responsible for printing — or anything else for that matter.

This sounds like a real problem a Generator might be able to solve! It’s worth noting that it’s certainly possible to implement what I need without a Generator. However in this instance I found the Generator implementation more concise and easier to reason about:

Here we can express our recursion in a simpler non-continuous way. Our type function now knows nothing about printing our value, nor does it implicitly deal with the side effects of printing.

For completeness, here is a mock implementation of the full problem with our concerns separated. Depending on the age of your browser, you should be able to copy and paste this directly into your console to see it running:

This example features an asynchronous implementation of the print function for simulating a human typing a phrase. We add a short defined pause between each iteration of the string. Now our print function can deal with just printing, whilst our type function can deal with just typing.

Further Reading

One point that I have not touched on with my implementation that I thought was worth an honourable mention is the ability to iterate over a collection where its length is not known ahead of time. The ability to pause the execution of an iteration in this scenario is critical when thinking about performance and memory consumption.

To put this in the context of my typing example, think how we would go about this if we did not know the phrase ahead of time, or if we did not know for sure that the phrase didn’t contain an infinite amount of characters. We might want to pause the current iteration to make sure we have enough resources to deal with the next iteration.

Conclusion

I hope this goes some way towards helping understand Generators for those, who like me, were left mildly confused when the specification was first introduced. This example barely scratches the surface of the possibilities available with Generators and Iterators, so I’d encourage looking at the MDN documentation and to start thinking where they might provide brevity in your real-world problems.