tech.kayac.com Advent Calendar 2014 14日目です!
ざっくばらんにES6のジェネレータのことを書きます。
実のところそんなにやばくないです。
ECMAScript 6!
ECMAScript 6、盛り上がってきましたね。
クラスやアロー演算子、let装飾子など、
魅力的な機能がいくつも追加されるES6ですが、
今回は、その中でもジェネレータについて書きます。
ジェネレータ!
ECMAScript 6で追加される新たな機能や構文、
"新たな"とはいえ、その多くが、シュガーシンタックス程度に留まっているのと比べ、
ジェネレータは、処理そのものの流れを大きく変えます。
ジェネレータはとりわけ、非同期処理を書くときに役に立ちます。
Promisesやasyncが、非同期処理を並べる程度の解決しかできないのに対して、
ジェネレータは、ネストを一切せずに、非同期処理を書くことができるようになります。
ジェネレータを利用した非同期処理は、今後主流になっていく気がするので、
2014年中に抑えておきましょう!
ジェネレータの基本
ジェネレータの基本的な使い方は、以下のとおりです。
// ジェネレータを返す関数を定義する function *func(){ yield 1; yield 2; yield 3; console.log(yield 5); // -> foo } // ジェネレータを取得する var gen = func(); console.log(gen.next().value); // -> 1 console.log(gen.next().value); // -> 2 console.log(gen.next().value); // -> 3 console.log(gen.next().value); // -> 5 console.log(gen.next("foo").value); // -> undefined
出力は以下のようになります。
1 2 3 5 foo undefined
ジェネレータの使い方
関数定義のとき、頭に*をつけることで、
ジェネレータを返す関数を定義できます。
そして、ジェネレータの中では、処理を中断するためのキーワードyield式が使えるようになります。
関数を呼び出すと、ジェネレータが返ります。
ジェネレータは、nextというメソッドを持ち、
これを呼び出すことで、次のyield式まで処理を進め、返り値 "value"などを含めたオブジェクトを返します。
そして、次にnextを呼んだときは、前回のyield式から処理を再開します。
また、nextに引数を与えることで、
再開するyield式の値とすることができます。
ジェネレータの応用
さて、実際のところ、ジェネレータは
RubyやPythonなど、すでに多くのスクリプト言語で実装されていて、
プログラミングに精通している人ならば、
「あーあれね、やっとJSにもそういうのが追加されるのか。便利だね」
程度にしか考えないかもしれません。
しかし、コールバック地獄に苦しめ続けられてきたJavaScript界にとって
ジェネレータは、地獄に射す一筋の光に他なりません。
ジェネレータでコールバック地獄を解消する
一見して、ジェネレータはコールバック地獄と関係ないように見えますが、
応用することで、とても強力な道具になります。
重要なアイディアは、メインの処理をジェネレータにし、
それを外側からコントロールする構造を作ることです。
非常に言葉で説明しづらいので、コードを貼ります。
function *main(){ console.log("wait 1000ms"); yield timeout(1000); console.log("calc 2*2"); var four = yield getPower(2); console.log(four); } var timeout = function(time){ return function(callback){ setTimeout(callback, time); } } var getPower = function(value){ return function(callback){ setTimeout(callback, 3000, value*value); } } var gen = main(); var resume = function(arg){ var thunk = gen.next(arg).value; if(thunk) thunk(resume); } resume();
上のコードを実行すると、 間隔を置きながら、次のような結果が出力されます。
wait 1000ms calc 2*2 4
ネストが消えた!
ご覧の通り、timeoutなどの非同期処理を挟んでいるにもかかわらず、
メインの関数からはネストが消えました。
yield式が挟まっているので、最初はぎょっとするかもしれませんが、
慣れれば、非同期でネストしまくるコードよりも、はるかにわかりやすいはずです!
ライブラリを利用する
この仕組みを利用した代表的なライブラリにcoがあります。
coでは、上記と同じような書き方ができるのに加え、
エラーの取り回しや、Promisesとの連携などができるようになっています。
実際に取り入れるときは、こういったライブラリを活用しましょう。
ポリフィル
さて、紹介してきたジェネレータですが、
当然、古いIEなど、ジェネレータを実装していないブラウザでは利用できません。
それでもフロントで利用したい!場合には、
regenerator や traceur-compiler を使って、
ES5向けのコードに変換することもできます。
とはいえ、ジェネレータは最初に書いたように、かなり複雑な仕様。
少しばかりのソースの肥大化は許容しないといけないかもしれません。
終わりに
ジェネレータは、JavaScriptの非同期処理を大きく変える可能性のある機能です。
一方で結構癖があるのも事実なので、早めに触って慣れておくことをおすすめします!
明日は
次回はYAPC 2014で公演した@mackee_wさんです。 よろしくお願いします!