JavaScript における Generator

ES2015 にて導入された Generator の使い方や、そのメリットなどを紹介します。

Generator

JavaScript の Generator は function* 構文を利用して簡単に記述する事が出来ます。

function* idMaker() {
  var index = 0;
  while(true)
    yield index++;
}

作成されたGenerator は呼び出しにより生成され、 next() メソドをコールする形で内部の状態を遷移させることが出来ます。

var gen = idMaker();

console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2

next() メソドの戻り値は valuedone の存在するオブジェクトで、yieldの右辺に定義した値が value に格納されます。

また、next() に引数を添える場合 yeild の左辺で受け取ってジェネレータ内で利用することも出来ます。

反復可能オブジェクト

JavaScript では、 for...of や スプレッド演算子 ... で取扱可能なオブジェクト(反復可能オブジェクト)は、 Array や Map などの一部に限られており、 Generator や Object をそのまま for...of などに利用することは出来ません。

ジェネレータを for 文で利用するためにはカスタムの 反復可能オブジェクトを作成します。

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};

for (let value of myIterable) { 
    console.log(value); // 1, 2, 3
}
// or

[...myIterable]; // [1, 2, 3]

Promise と Generator

Generator は 関数内で状態を記録しながら、 yield 演算子を区切りに関数をステップ実行する事が出来ます。

この性質を利用して、 Generator は Promise の並列実行でも利用される場面がアリます。

次のような 複数の Promise を 直列に実行したいケースなどで以下のように記述することができます。

const myTimer = (time)=>{
    return new Promise((resolve)=>{
        setTimeout(()=>{
            resolve(time)
        },time)
    })
}

myco(function*(){
    const result = [];
    result.push(yield myTimer(1000));
    console.log(result) // [ 1000 ]
    result.push(yield myTimer(900));
    console.log(result) // [ 1000, 900 ]
    result.push(yield myTimer(500));
    console.log(result) // [ 1000, 900, 500 ]
})

ここで myco は簡単に以下のような記述で実装することが出来ます。

var myco = (gene)=>{
    var g = gene()
    let result = g.next()
    result.value.then((res)=>{
        result = g.next(res)
        result.value.then((res)=>{
            result = g.next(res)
            result.value.then((res)=>{
                result = g.next(res)
                result.value.then((res)=>{
                    // more and more ...
                })              
            })      
        })
    })
}

next で引数に渡した値は、 yield の戻り値として受け取ることが可能なため、再帰的な呼び出しで次々に promiseを受け取り、 then の中で次の next を呼び出しています。

この処理は、 co と呼ばれるモジュールで実装されており、コレを利用することでシンプルに記述することが出来ます。

const co = require("co")

co(function* () {
  var result = yield Promise.resolve(true);
  return result
}).then(function (value) {
  console.log(value)
})

https://www.npmjs.com/package/co

Promiseを用いた非同期処理の直列実行を同期的に記述する方法としては async/await 文法も用意されており、最近ではそちらが利用されるケースが多いでしょう。

参考

https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Iterators_and_Generators

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です