Generators in JS
Tags: javascript, programming, tutorial
Published: 2022-03-16 23:18:58


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();
```

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}
```
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}
```
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
```
And that's about it! Happy coding!