An Introduction to JavaScript Generators
Tags: javascript, generators
Published: 2019-12-19 18:18:51

One of the fundamentals of JavaScript is that it is single-threaded, meaning that two pieces of code cannot run at the same time. If we call a function, we expect it to run to completion, blocking any other code from running. This presents challenges for any task where you need to wait for something to happen (for example, waiting for an API response). We have different tools at our disposal to help with this, including callback functions, promises, and more recently `async/await`, introduced with ES8.

A lesser known, but still very powerful tool was introduced earlier, with ES6: generators. These are similar to `async/await` in that they let us write asynchronous code in a linear, straightforward fashion. However, they also provide the ability to **pause and restart a function**, without blocking the execution of other code — exactly what we’re used to not being able to do in JavaScript!

I first encountered generators through [redux-saga](https://redux-saga.js.org/), an excellent library for handling side effects in Redux. I was curious to learn about how they worked, and found them a little unintuitive at first. I spent some time digging into them, and in this post I’ll share what I found.

You may recognize them from their somewhat unique syntax, with a star after the function declaration and the use of the `yield` keyword (which can only be used within a generator function):

```
function* generatorFunc() {
  yield;
}
```

As their name suggests, generators _generate_ a sequence of values. Each time a generator is paused, it returns a new value, and each time it’s restarted it can take in a new argument. Following how the input and output are used can be a little tricky, so I’m going to focus on these two aspects, breaking down how generators both generate and consume data.

## Generating data

Generators are a type of **iterator**, which are objects that define a sequence (one example is the [array iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/@@iterator). Iterators must have a `next()` method, which is used to traverse the sequence. Each time `next()` is called it returns an iterator response, which specifies whether the sequence is **done** as well as the next **value** in the sequence (or the return value if the sequence is done).

```
const iterator = {
  next: () => ({
    value: any,
    done: boolean
  })
}
```

_Learn more about the [iterator protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterator_protocol)._

Generators have additional behavior: they are a specific kind of iterator, returned by a **generator function**. When the iterator’s `next()` method is called, the generator function will execute until it reaches one of the following:

- `yield` keyword (pauses the execution)
- `return` statement (ends the execution)
- end of the generator function (ends the execution)
- `throw` keyword (throws an exception)

Here’s an example (with `throw` omitted for simplicity):

```
function* generatorFunc() {
  yield 1 + 1;
  return 2 + 2;
}

// 1.
const generatorObj = generatorFunc();

// 2.
generatorObj.next();
// returns { value: 2, done: false };

// 3.
generatorObj.next();
// returns { value: 4, done: true };
```

_View code in a [jsfiddle](https://jsfiddle.net/9zmuk28j/12/)_

Let’s break down what’s happening:

1. The generator is created

2. `next(`) is called for the first time:
    - The generator function evaluates up to the first `yield`, and then pauses
    - `value` is the result of the expression following `yield`
    - c. `done` is false because we haven’t reached a return statement or the end of the generator function

3. `next()` is called for a second time:
    - The generator function evaluation resumes
    - The `return` statement is reached
    - `value` is the result of the `return` statement
    - `done` is true, and the generator object has been consumed

The sequence of values can also be retrieved without calling `next()` explicitly, using array destructuring, the spread operator, or a simple `for` loop:

```
function* generatorFunc() {
  yield 1 + 1;
  yield 1 + 2;
 
  return 2 + 2;
}

const [a, b, c] = generatorFunc();
// a = 2, b = 3, c = undefined

const values = [...generatorFunc()];
// values = [2, 3];

const vals = [];
for (const val of generatorFunc()) {
  vals.push(val);
}
// vals = [2, 3]
```

_View code in a [jsfiddle](https://jsfiddle.net/L8r3wxy2/1/)_

One important note here is that these three ways of retrieving values from a generator only take into account the `yield` expressions, ignoring the value from the `return` statement.

## Consuming data

So far we’ve looked at how generators passively generate a sequence of values; now, let’s focus on how they take in data. Most standard iterators cannot accept arguments (e.g. array iterators or set iterators), but generators can, by passing an argument to `next()`.

```
function* generatorFunc() {
  const a = yield 1 + 1;
  const b = yield 1 + 2;
 
  return 2 + 2;
}
const generatorObj = generatorFunc();

// 1.
generatorObj.next(‘value 1’);
// returns { value: 2, done: false }

// 2.
generatorObj.next(‘value 2’);
// returns { value: 3, done: false }
// a = ‘value 2’

// 3.
generatorObj.next();
// returns { value: 4, done: true}
// b = undefined
```

_View code in a [jsfiddle](https://jsfiddle.net/v2x4k8s9/4/)_

Let’s break down the order of execution in a more granular way. We’ll start by focusing on the value of the variables assigned to the `yield` expression, and the value from the iterator response returned from `next()`:

1. `next()` is called for the first time, with an argument of `'value 1'`
    - It reaches the first `yield` and pauses
    - The value returned by `next()` is the result of the expression following the first `yield`

2. `next()` is called for the second time, with an argument of `'value 2'`
    - The argument provides the value of the constant assigned to the first yield statement (therefore `a = 'value 2'`)
    - It reaches the second `yield` and pauses
    - The value returned by next() is the result of the expression following the second yield

3. `next()` is called for the second time, with no argument
    - There is no argument to provide the value of the constant assigned to the second yield statement (therefore `b = undefined`)
    - It reaches the `return` statement and ends
    - The value returned by `next()` is the result of the return statement

The most important thing to grasp here is that the argument to `next()` provides the value for the `yield` that had previously paused execution of the generator function. The argument passed to the first `next()` call is ignored.

## Summary

![Diagram showing the input and output of generators. All content is included in the text.](https://thepracticaldev.s3.amazonaws.com/i/aegr8yz1c8b8spn08ab6.png)

Here’s a quick summary of the main takeaways from this post.

Generators:
- pause with `yield` and restart with `next()`
- return a new value each time the function pauses or ends
- set each return value based on the expression following the `yield` that paused the function
- take in data through arguments passed to `next()`
- set the value of the variable assigned to a `yield` statement based on the arguments passed to the `next()` call that restarted the function

I hope you’ve enjoyed this quick dive into generators! If you want to dig in deeper, I recommend reading the [Generators chapter of ‘Exploring ES6’](https://exploringjs.com/es6/ch_generators.html) by Axel Rauschmayer, which was very helpful in writing this article. If you want to see generators in use, [redux-saga](https://redux-saga.js.org/) is definitely worth checking out as well.

Let me know in the comments how you’ve used generators, or if you have any questions!

_This post was originally posted on the [Giant Machines blog](https://www.giantmachines.com/blog/an-introduction-to-javascript-generators)._