javascriptのyieldとジェネレータ関数について

## 概要
yieldおよびジェネレータ関数について勉強したことをまとめてみます。

## yieldとは
- Javascriptにおける**ジェネレータ関数**で処理を一時停止したり再開したりするのに使われるもの
- 一般的な関数の**return**に相当するもので、ジェネレータ関数では**yield**を使用する
- returnと違って関数内で何回でも呼び出すことができる
- 参考：[yield](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield)

## ジェネレータ関数(Generator)とは
- **イテレータ**を生成する関数
- イテレータとは配列のような**繰り返し処理できるもの**のこと
- 書き方

```javascript
/* ジェネレータ */
function* testGenerator() {
    for(let i=1; i<=10; i++){
        if (i % 2 == 0)
            yield i;
    }
}

// イテレータを生成
g = testGenerator()

// 繰り返し処理
for (let even of g) {
    console.log(even);
}

// next()を使用する場合
// 上記繰り返し処理ですべての要素を呼び出したので、もう一度作り直す
g = testGenerator()

// next()の返り値は  { value: 2, done: false } のようになっている
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: 4, done: false }
console.log(g.next()); // { value: 6, done: false }
console.log(g.next()); // { value: 8, done: false }
console.log(g.next()); // { value: 10, done: false }
console.log(g.next()); // { value: undefined, done: true } valueがundefinedになってdoneは初めてtrueになる
```

## あらためてyieldとは
- ジェネレータ関数.next()では
  1. **一回目の呼び出しは最初のyieldで一旦処理をストップ。**
  2. **二回目は最初のyieldから二回目のyieldまでを処理しストップ。**
  3. **三回目は...(以下繰り返し)となる。**

```javascript
function* testGenerator() {
    console.log('yield 1!');
    yield 1;
    console.log('yield 3!');    
    yield 3;
    console.log('yield 5!');    
    yield 5;
}

g = testGenerator()

console.log(g.next().value);
console.log(g.next().value);
console.log(g.next().value);

// または
// for (let odd of g) {
//     console.log(odd);
// }

// 結果
// yield 1!
// 1
// yield 3!
// 3
// yield 5!
// 5
```

## yield*
- ジェネレータ関数内で別のジェネレータ関数を呼び出す際に使用する
- next()を使用した際の返り値は「呼び出したジェネレータ関数の順」になる

```javascript:
function* testGenerator() {
    yield 1;
    yield 3;
    yield 5;
}

function* testGenerator2() {
    yield 2;
    yield 4;
    yield 6;
}

function* parentGenerator() {
    yield* testGenerator();
    yield* testGenerator2();
}

g = parentGenerator()

for (let num of g) {
    console.log(num);
}

// 結果
// 1
// 3
// 5
// 2
// 4
// 6
```

## ジェネレータ関数に引数を渡したい
- ジェネレータ関数には引数を渡せます。
- 渡した引数は関数内のどこからでも参照できます。

```javascript
function* testGenerator(val) {
    console.log(val);
    yield 1;
    console.log(val + 1);
}

let g = testGenerator(123);

g.next();
g.next();

// 結果
// 123
// 124
```

- さらに**ジェネレータ関数.next(引数)とすることで渡すこともできます**
- 渡した引数はyield時に取り出すことができます。

```javascript
function* testGenerator() {
    let result = yield 1;
    yield result + 2;
}

let g = testGenerator();

console.log(g.next(g.next().value).value);

// 結果
// 3
```

## yieldを使った同期処理・非同期処理
- yieldの「処理を一時停止したり、再開したりする」機能を用いて**非同期処理を同期的に処理する**ことができます。
- 例えばこちらの関数

``` javascript
function async(){
    let count = 1;
    setTimeout(() => {
        console.log(count++);
        setTimeout(() => {
            console.log(count++);
            setTimeout(() => {
                console.log(count++);
            },1000)
        },1000)
    },1000)
}

async();

// 1秒毎に1, 2, 3と表示される
```
yieldを使えば以下のように書き換えられます

```javascript
function async(g, num){
    setTimeout(() => {
        console.log(num);
        g.next();
    }, 1000)
}

g = (function* () {
    yield async(g, 1);
    yield async(g, 2);
    yield async(g, 3);
})();

g.next();
```
元々ネストを浅くする目的で使われていたようですが、現在はasync, awaitが主流のようです。

```javascript
function test(num){
    return new Promise(resolve => {
        setTimeout(() => {
            console.log(num);
            resolve();
        }, 1000)
    });
}

async function g(){
    await test(1);
    await test(2);
    await test(3);
}

g();
```

## 疑問

### ジェネレータ関数内でreturnするとどうなるか

```javascript: 
function* testGenerator() {
    yield 1
    yield 2;
    return 'return';
    console.log('hello');
    yield 3;
}

g = testGenerator();
console.log(g.next());
console.log(g.next());
console.log(g.next());
console.log(g.next());

// 結果
// { value: 1, done: false }
// { value: 2, done: false }
// { value: 'return', done: true }
// { value: undefined, done: true }
```
- returnを呼び出した時点でdone: trueとなる
- return以降の処理は例えyieldの記載があっても呼ばれることはない。