DEV Community

Zachary Izdepski
Zachary Izdepski

Posted on

Generators in JS

It's been some time since ES6 first came out in 2015, and it feels like even longer when we consider the speed at which tech evolves. At this point, using key ES6 features such as arrow functions, let/const variable declaration, the spread operator as well as many more useful additions have become the norm for most javascript developers. But nestled amongst the more commonly known ES6 syntax is a less well-known and less understood feature: the generator function. So, in this blog post, I will walk through exactly what a generator is and provide some examples of how it can be used.

At its core, a generator is a function that returns a generator object. This generator object has a few built in methods that allow it to behave in ways that are unique in javascript. These include next, return and throw. Here is the syntax for creating a basic generator object:

function* basicGenerator() {
  yield 'first value';
  yield 'second value';
  yield 'third value';
}
const genObj = basicGenerator();
Enter fullscreen mode Exit fullscreen mode

The above genObj is a generator object that is one instance of a generator. Instead of using the return keyword, generators use yield to return objects that contain a value and a done property which evaluates to a boolean. To initiate a generator, we can call the next function. Every time next is called, the next operation is run and another value is yielded. When all next functions have been called, the done property flips from false to true.

console.log(genObj.next()); // -> {value: 'first value', done: false}
console.log(genObj.next()); // -> {value: 'second value', done: false}
console.log(genObj.next()); // -> {value: 'third value', done: true}
Enter fullscreen mode Exit fullscreen mode

The utility of a generator may not be immediately apparent, but if we consider that the context is saved between each next function call, we can start to imagine writing asynchronous code this way, as well as use them as iterators. Generators all but completely eliminate the need for callbacks and in doing so are a way to avoid callback hell. They can also be used to create controlled infinite loops, or open-ended processes that won't cause your computer to crash since the generator "pauses" after every next call. Consider the following example:

function* infiniteIDs() {
  let id = 0;

  while (true) {
    const increment = yield id;
    if (increment !== null) {
      id += increment;
    } else {
      id++;
    }
  }
}

const IDGenerator = infiniteID();

console.log(IDGenerator.next());// -> {value: 0, done: false}
console.log(IDGenerator.next(4));// {value: 4, done: false}
console.log(IDGenerator.next());// {value: NaN, done: false}
Enter fullscreen mode Exit fullscreen mode

In the above code snippet we create a generator that generates a new id every time next is run, which could be run ad infinitum since we have set a condition in our while loop to always be true. On the first next call, the generator yields 0. On the second call, we pass in a value to next that gets returned in the yield, so 4 is yielded. On the third call nothing is passed in to next so NaN is yielded since we didn't provide an increment. To reset the generator we could simply create a new instance of one by setting another variable equal to our generator function and give it whatever starting values we want. One important thing to note is that nothing would happen to the value of id if we passed a value to next on the first call since there is no yield yet to pass a value to. Now let's take a look at the return and throw methods.

Let's say we don't know how many ids we want to create so we're ok with the open-ended nature of our IDGenerator but we do want to break out of it under certain conditions or if an error is thrown. To break out of a generator we can call return and optionally pass it a value to be immediately returned and set the done property to true.

console.log(IDGenerator.return(6)); -> {value: 6, done: true}
console.log(IDGenerator.throw(new Error("invalid input"))) -> Error: invalid input
Enter fullscreen mode Exit fullscreen mode

And that's about it! Happy coding!

Top comments (0)