Generatorsについて
Symbol、Iteratorと順番に説明してきました
最後に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のあとに*(アスタリスク)を記述する
- yield、yield*を使う
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*を使う
- 非同期処理を同期的に書ける
まだ自分でもそこまで把握できてない部分があるので、間違ってる箇所があればご指摘ください!
基本的な部分はおさえてあるかなって思ってます