You’ve probably heard of ES6 generators, perhaps you’ve even learned the syntax, and you may be wondering what they’re actually useful for in real life.
Definition (from MDN)
Generators are functions which can be exited and later re-entered. Their context (variable bindings) will be saved across re-entrances.
You may be thinking, “Okay, but why would I want to do that?” As it turns out, there are a whole range of use cases ranging from simple to complex, many of which involve Promises to make asynchronous requests (async/await is built on top of generators). My goal is to give you the first baby step into understanding how they work with a simple, real-life example so that you begin noticing when a generator is the most suitable solution to problems in your own code. Here we go.
I’m building an app in which my users can calculate a 3-week workout cycle, with a setting to work out between 3 and 7 days per week during the cycle. Each individual workout is based on one of the following 4 lifts: squat, bench press, deadlift, and overhead press, and each successive workout must be based on the next lift in that order:
- Overhead press
- Overhead press
You can probably see where this is going.
I need my code to say, “Give me the lift for the next workout, then the next, then the next, etc. When the end of the list of lifts is reached, start over from the beginning and keep repeating forever, until I’ve generated all the workouts for the 3-week cycle.” Here’s a simplified version of how I initially implemented it, without generators:
Not too bad, but it could be more declarative. Wouldn’t it be nice if we didn’t have to keep track of that currentLiftIndex directly in our workout generation code? It decreases the readability of the code and feels like it belongs in its own function. Here’s the code using a generator function, I’ll explain it below.
Here’s what’s happening:
repeatedArray returns an iterator object for the repeatedArray function itself (read that twice) when we call it on line 9. The iterator is stored in a variable named nextLiftGenerator. It’s important to understand that the code in the function hasn’t been executed at this point. The function is only executed when we call the next() function on the nextLiftGenerator iterable, and it’s only executed up until it hits a yield. Our generator gives us the value, then waits until the next call to continue execution until it hits another yield, then returns that value. Make sense? That’s it!