【JavaScript】ジェネレーターって何？

# ジェネレーター関数とは
ジェネレーターを理解するためには、先にジェネレーター関数について知っておくと理解が早いと思います。

## ジェネレーター関数とは
ジェネレーター関数は**イテレーターを作るための関数**です。
これは`function*`（最後に`*`がつく）キーワードによって定義できます。

```js
function* 関数名(引数) {
  // 処理
}
```

<details><summary>イテレーターとは</summary>

イテレーターは**何かを列挙する**ためのオブジェクトです。
`next`メソッドを持ち、このメソッドが`{ done: boolean, value: T }`形式のオブジェクトを返す場合、それはイテレーターと呼べます。

また、`Symbol.itereator`メソッドを持ち、これがイテレーターを返す場合、そのオブジェクトはイテラブルだと呼べます。
イテラブルなオブジェクトは`for...of`やスプレッド構文が使えます。

詳細は[MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Iteration_protocols)をご覧ください。
<!-- 前日の記事をここに貼る -->

</details>

また、ジェネレーター関数は後述の[ジェネレーター](#ジェネレーターとは)を返します。
ジェネレーターはとりあえずイテレーターのようなものだと思ってください。

### `yield`
ジェネレーター関数において、イテレーターの`next`メソッドが返す値は[`yield`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/yield)を使うことで指定できます。
`yield`は通常の関数で使うことはできません。

構文は`yield 返したい値`です。

```js
function* numbers() {
  yield 1
  yield 2
  yield 3
}
```

## 例: 1~10を表現する

例えば、1~10を表現するイテレーターを作成したいと思います。
イテレーターは使い捨てにしたいので、簡単にイテレーターのオブジェクトを作成できる方法を用意する必要があります。

今回は`makeIterator`というジェネレーター関数を用意し、これを呼び出すと新しくイテレーター（ジェネレーター）が作成されるようにします。

### 普通に実装した場合
ジェネレーター関数を使わず普通に実装した場合は、おおよそ以下のようになると思います。

```js
function makeIterator() {
  // クロージャで状態を管理
  let current = 0
  
  // イテレーターを返す
  return {
    next() {
      current++
      // すでに10を超えている場合
      if (current > 10) return { done: true }
      return { done: false, value: current }
    }
  }
}

const iterator = makeIterator()
iterator.next() // { done: false, value: 1 }
iterator.next() // { done: false, value: 2 }
// ...
iterator.next() // { done: false, value: 10 }
iterator.next() // { done: true }
```

この方法だとネストが深くなり、少し冗長な印象があります。

### ジェネレーター関数を使った場合

```js
// function*キーワードでジェネレーター関数を定義
function* makeIterator() {
  let current = 1
  while (current <= 10) {
    yield current
    current++
  }
}

const iterator = makeIterator()
iterator.next() // { done: false, value: 1 }
iterator.next() // { done: false, value: 2 }
// ...
iterator.next() // { done: false, value: 10 }
iterator.next() // { done: true, value: undefined }
```

ジェネレーター関数では、普通の`while`の中に`yield`を入れることで、簡潔に実装できます。
ネストも減ってコードも読みやすくなったと思います。

# ジェネレーターとは
ジェネレーターは、**ジェネレーター関数を呼び出すと得られるオブジェクト**です。

ジェネレーターは以下の性質を持っています。

- **イテレーターである**: `next`メソッドを持ち、その戻り値が[イテレータープロトコル](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Iteration_protocols#イテレータープロトコル)に従っているため
- イテラブルである: `[Symbol.iterator]`メソッドが`this`=イテレーターを返すため（[反復可能プロトコル](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Iteration_protocols#反復可能プロトコル)に従っているため）

上で定義した`makeIterator`関数はジェネレーターを返します。
そしてジェネレーターはイテレーターなので、`next`メソッドを呼び出すことができました。

## イテラブルである
ジェネレーターはイテラブルなので、`for...of`文やスプレッド構文が直接使えます。

```js
const iterator = makeIterator() // ジェネレーターを作成
const array = [...iterator] // スプレッド構文で配列に変換
// [1, 2, 3, ..., 9, 10]
```

もしジェネレーター関数を使わずに同じことをやろうとすると、少し大変です。
イテレーターは通常イテラブルではないので、`[Symbol.iterator]`メソッドを持ったオブジェクトでラップする必要があります。

```js
function makeItereator() {
  // ジェネレーター関数を使わずに実装する
}

function makeIterable() {
  return {
    [Symbol.iterator]() {
      return makeIterator()
    }
  }
}

const iterable = makeIterable()
const array = [...iterable]
// [1, 2, 3, ..., 9, 10]
```

もしくは`[Symbol.iterator]`メソッドに実装を書くことになるでしょう。

# 作例: `range`

ジェネレーター関数なら、範囲を示す`range`の簡単な実装もできます。
ここでの`range`は以下の引数を取ります。

1. `start`: 範囲の開始地点
1. `end`: 範囲の終了地点、`end`自身は含まない
1. `step`: 範囲内の数値をいくつづつ増やすか、デフォルトでは`1`

実装は先ほどとほぼ同じようにできます。
ここでは以下のようになりました。

```js
function* createRange(start: number, end: number, step: number = 1) {
  let current = start // currentを初期化
  // currentがendを超えるまでyield
  while (current < end) {
    yield current
    current += step // stepだけcurrentを増やす
  }
}
```

```js:使用例
const range = createRange(1, 11); // 1 ~ 10（11は含まない）
[...range] // [1, 2, 3, ..., 10, 11]
```

この`range`にはジェネレーターが入っています。
ただのジェネレーターなので、スプレッド構文に入れたり、直接`for...of`ループに入れたりできます。