こんにちは。フロントエンドのまろCです。
今回はES6のgeneratorという機能についてご紹介いたします!
generatorとは
ジェネレーターは処理を抜け出すことも後から再帰することもできる関数です。ジェネレーターのコンテキスト (変数の値)は再帰しても保存されます。
ジェネレーター関数を呼び出しても関数は直ぐには実行されません。代わりに、関数のためのiterator オブジェクトが返されます。iteratorのnext() メソッドが呼ばれると、ジェネレーター関数の処理は、イテレーターから返された値を特定する最初のyield演算子か、ほかのジェネレーター関数に委任する yield*に達するまで実行されます。next()メソッドは 、生産された値を含むvalueプロパティとジェネレーターが最後の値を持つかを示すdoneプロパティを持つオブジェクトを返します。
すこし難しいですね。もう少しやわらかく解説すると、generatorとは関数内にブレークポイント的なものを設けて、そこで一旦抜けたり、また処理を再開したりといったことが可能になるものです。処理の終了を待って、次の処理を呼び出すわけですね。
generatorの使いどころ
Web制作の場面でよく使われるのが、ajaxでの非同期通信の置き換えです。非同期で情報を取得して、その結果を受けて更に情報を取得し、描画するといった非同期通信を同期的に記述することができます。
jQuery.Deferredを使っていた方はわかると思いますが、非同期通信を同期的に記述するという使い方がこの機能を理解するにベターな方法かと思いますので、次章ではそこをコードと一緒に解説できたらと思います。
環境について
- OS: MacOSX 10.10.5
- Nodejs: v4.2.2
筆者の環境はこちら。gulpを使ってbabelとwebpackを使ってビルドしていきます。
Uncaught ReferenceError: regeneratorRuntime is not defined
webpackを使う場合、そのままビルドしようとすると、このようにエラーとなります。
npm install babel-plugin-transform-regenerator --save-dev npm install babel-plugin-transform-runtime --save-dev
ですから、フロント側でbabel+webpackを使用する場合には、こちらのプラグインを追加インストールしましょう。
webpack.conf.js
{ test: /\.jsx?$/, exclude:/node_modules/, loader: 'babel', query: { presets: ['es2015'], plugins: ['transform-runtime', 'transform-regenerator'] //これを追記 } }
それからwebpackのconfigに、追加したプラグインを使用する旨を追記します。
generator関数の解説
ここからは、簡単にgenerator関数の使い方を解説していきます。キーワードは「*」と「yield」、next()です。
generator関数を定義する
関数をgenerator関数にするには、functionの後ろに「*」をつけるだけです。
function* getWord() { }
ここで注意したいのは、generator関数はアローファンクションでは使用できないということです。「function」を記述して使用します。
一時停止と再開
generator関数内で処理を途中で抜けられるようにするために「yield」というキーワードを使用します。
function* getWord() { yield 'hoge'; return 'foo'; }
このように、抜けたいところの値、(オブジェクトや関数でもよい)の前に「yield」と記述することで、この関数を呼び出した際、その後ろの値を取得できます。
値の取得
yieldでした値を取り出すにはnext()関数を使います。
まずは、関数を呼び出します。
let generator = getWord();
これだけでは、実行されません。next()関数を使うことで、最初のyieldまで処理を進めます。
let word = generator.next();
こうすると、wordの中にはこのようなオブジェクトが返されます。
{ value: 'hoge', done: false}
もう一度、generator.next()を実行すると、次のyieldまで、あるいはyieldがなければ最後まで処理をおこないます。そして、{ value: 'foo', done: true}という値が返されます。
valueと、done
valueにはyieldで指定した値が入ります。doneには、その関数内で処理を最後までしたかをbool値で返却します。
ここまで、キーワードを元に簡単な処理の流れを解説しました。
非同期通信でつかってみよう
実際にWeb制作で使うコードを、詳しく見ていきたいと思います。
使用する場面は、リストの最下部にもっと読むボタンがあったとして、続きのhtmlをajax通信で取得し、リストのappendするというもの。ajax通信はjQueryを使います。(ここではPromiseについては言及しませんので、ご了承ください。)
全ソース
//ajax通信をするジェネレーター関数 function* getData() { //ajax通信をおこなう yield $.ajax({ type: 'get', dataType: 'html', url: './page/2', cache: false }); //描画をおこなう yield (data) => { let $data = $(data); $('#js-list').append($data.find('#js-list')); }; } //処理 let generator = getData(); generator.next().value.then((data)=>{ generator.next().value(data); });
jQuery.ajaxをジェネレーター関数内に
コード内の処理のところから見ていきます。
最初に定義したgenerator関数を呼び出します。最初のgenerator.next()ではgetData()内の$.ajaxが実行されます。そこで処理は止まります。
velueの中には$.ajaxのオブジェクトが入っていますので、これまでと同様、.then()関数を使い、htmlを取得します。next()の戻り値はオブジェクトなので、変数に入れなくてもチェインメソッドでつなげて記述することができます。
呼び出した値を描画
次の.next()関数で描画をおこなう処理をします。
最初の.next()で取得したhtmlを引数に、呼び出しをおこないます。valueの中に描画する無名関数が入っているので、dataを引数に実行します。
注意点
next()関数では、yieldの後の値が取得できるだけです。それを操作したり、実行したりするためには、valueの中にアクセスしなければなりません。
まとめ
実際の案件で使える「もっと読むボタン」の処理を、generatorで書いて、解説しました。やはり利点は、同期的に書けることです。
最初はよくわからなかったのですが、こうしてよく使うフロントエンドのことで書くとわかりやすいですね。それでは。
LIGはWebサイト制作を支援しています。ご興味のある方は事業ぺージをぜひご覧ください。