Generatorsについて

SymbolIteratorと順番に説明してきました
最後にGenerators(yield)について説明します

Generatorsとは

ジェネレータとはイテレータを強力にサポートするものです

例えば、イテレータを使って1から10まで順番に表示する場合

let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for (let n of array) {
    console.log(n);
} 

for文使ったり、関数化すればもっと簡潔に書けますが
イテレータを使うことが前提の場合こんな感じで書けます

ジェネレータを使うとスマートに書けます

function* gfn(from, to) {
    while(from <= to) {
        yield from++;
    }
}

var g = gfn(1, 10);
for (var n of g) {
    console.log(n);
}

上記のgfnがジェネレータ関数といい、gfnから得られるオブジェクトをジェネレータといいます

ジェネレータはIterableでもあり、Iteratorでもあります

g[Symbol.iterator]
/*
[Symbol.iterator]() { [native code] }
*/

g.next()
/*
Object {value: 1, done: false}
*/

ジェネレータ関数の書き方

function* gfn() {
    yield 1;
    yield 2;
    yield 3;
}

var g = gfn();
console.log(g.next()); // Object {value: 1, done: false}
console.log(g.next()); // Object {value: 2, done: false}
console.log(g.next()); // Object {value: 2, done: false}
console.log(g.next()); // Object {value: undefined, done: true}

ジェネレータ関数を実行すると、ジェネレータを返すだけで関数自体は実行されません
nextメソッドが呼ばれると、次のyieldまで処理が進み IteratorResultが返され処理が一時停止します
最後まで処理が進むとdoneがtrueになります

ジェネレータの内部状態の変更

nextメソッドに値を渡すと、ジェネレータが一時停止する直前のyieldの結果となります

function* gfn() {
    var counter = 0;
    while (true) {
        var reset = yield counter++;
        if (reset) {
            counter = 0;
        }

    }
}

var g = gfn();
console.log(g.next()); // Object {value: 0, done: false}
console.log(g.next()); // Object {value: 1, done: false}
console.log(g.next()); // Object {value: 2, done: false}
console.log(g.next(true)); // Object {value: 0, done: false}
                           // trueを渡したので、yieldが結果がtrueとなりresetされた
console.log(g.next()); // Object {value: 1, done: false}

yield*

yield*にはIterableなオブジェクトを渡すことが可能です
前回説明しましたが、String、Array、TypedArray、Map、SetなどがIterableなオブジェクトです

function* gfn() {
    yield 1;
    yield* [2, 3, 4];
    yield* 'abc';
}

var g = gfn();
console.log(g.next()); // Object {value: 1, done: false}
console.log(g.next()); // Object {value: 2, done: false}
console.log(g.next()); // Object {value: 3, done: false}
console.log(g.next()); // Object {value: 4, done: false}
console.log(g.next()); // Object {value: "a", done: false}
console.log(g.next()); // Object {value: "b", done: false}
console.log(g.next()); // Object {value: "c", done: false}
console.log(g.next()); // Object {value: undefined, done: true}

yield*に渡された値を反復し、その値によってyieldを呼び出すイメージです 下記のような感じ

function* gfn() {
    var array = [2, 3, 4];
    for (let n of array) {
        yield n;
    }
}

また、冒頭でも書きましたが、ジェネレータはIterableでもあるのでyield*に渡せます

function* g1() {
    yield 1;
    yield* g2();
    yield 5;
}

function* g2() {
    yield 2;
    yield 3;
    yield 4;
}

var g = g1();
console.log(g.next()); // Object {value: 1, done: false}
console.log(g.next()); // Object {value: 2, done: false}
console.log(g.next()); // Object {value: 3, done: false}
console.log(g.next()); // Object {value: 4, done: false}
console.log(g.next()); // Object {value: 5, done: false}
console.log(g.next()); // Object {value: undefined, done: true}

ジェネレータを使った非同期処理

ジェネレータを使うと非同期処理を同期的に記述することができます

function async(gfn) {
    var g = gfn();
    (function next() {
        var result = g.next();
        if (!result.done) {
            result.value.then(next);
        }
    })();
}

function sleep(msec) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            resolve();
        }, msec);
    });
}

async(function* (){
    console.log('start');
    yield sleep(1000);
    console.log('finish');
});

yieldにPromiseを渡すことで、Promiseがresolveされるまで処理が待機されます
ただ、上の例だとPromiseに限定した使い方しかできないので
coという便利なライブラリを使った方が確実です

まとめ

  • イテレータを強力にサポートするもの
  • Iterableでもあり、Iteratorでもある
  • functionのあとに*(アスタリスク)を記述する
  • yield、yield*を使う
  • 非同期処理を同期的に書ける

まだ自分でもそこまで把握できてない部分があるので、間違ってる箇所があればご指摘ください!
基本的な部分はおさえてあるかなって思ってます