ES2015 の機能を使用したイベントフローの制御


Windows 10 Anniversary Update に搭載される Edge がサポートする ECMA Script 2015 (ES2015) の機能を使用した JavaScript の非同期処理の制御について紹介します。

なぜ今、非同期処理の制御が必要なのか?

前回の記事「de:code2016セッション「モダン Web: たった今と、ほんの少し未来の話」フォローアップ」で紹介したように、Single-page Application (SPA) のようなモダンな Web アプリケーションでは、クライアントサイドのロジックとサーバーサイドのロジックの関係は疎結合となるため、処理は非同期を前提として考える必要があります。

そして、非同期処理の完了の通知は JavaScript ではイベントによって行われます。

たとえば、Web API をコールするには、XMLHttpRequest を使用して Web サーバーにリクエストを出し、イベントハンドラ内で完了のステータスを待って値を取り出します。

上記のような処理は、ひとつの大きな単位での処理を完了するのに、1 回で済むとは限らず、複数回発生する可能性があります。

そういった処理を実装する場合には、最終的な処理の完了までに実行される各々の処理の整合性 (順番とタイミング) を保つために、イベントハンドラをネストすることになり、その間に画像のロードやユーザーのアクションについてのイベントのハンドラが入ると、コードはひたすら右側に伸びていくことになります。これでは記述も煩雑になるだけでなく、デバッグも大変です。

これがいわゆる「コールバック地獄」とよばれるものです。

こういった制御を行うために、JavaScript のライブラリやフレームワークが様々な機能を提供してきました。
しかし、今最新のモダンブラウザーがサポートしつつある ECMA Script の 2015 や、2016 にはそういった機能が標準で用意されています。

JavaScript によるイベントフロー制御

非同期を制御するための機能として、Ecma Script 2015 (旧 ES6) では以下の機能が標準で提供されています。

今回はこの 3 つの機能についてサンプルコードを交えて紹介します。

なお、これらのサンプルコードは Internet Explorer のいかなるバージョンでも動作しませんのでご注意ください。

Edge の場合は Windows Insider Preview Build 10240+ か、Windows 10 Anniversary Update 以降のものを使用してください。

この記事のサンプルコードで解決する課題

制御すべき非同期イベントとしては、以下の 3 つの setTimeOut 関数を考えます。

これらの 3 つの setTimeOut 関数には、実行までの時間を指定する第二引数に上から 3 秒、2 秒、1 秒をセットしているので、コンソールへの文字列の出力は C, B , A となり、コードを記述した順番とは異なります。

これをコードを記述した順の A, B, C とコンソールに出力させるような記述を考えます。

コールバック

非同期処理の完了がイベントで通知されるならば、後続の関数の呼び出しはそのイベントハンドラ内で行えば良いことになります。

上記を踏まえ、従来だと以下のように、コールバックルーチン内に次の処理の呼び出しを書いていくことになります。

しかしこれだと、前述したように、ひとつの大きな単位での処理を完了までに扱う非同期の数が増えると、コールバックルーチンのネストが深くなり保守性が落ちていきます。

 

Promise

Promise は非同期処理を抽象化したオブジェクトです。

Promise を使用すると、以下のように非同期処理をメソッドチェーンで記述していくことができ、かつ、前回の処理の成否によって処理を別けることもできます。

 new Promise(()=>{1回目の処理}).then(2回めの処理,エラー処理).then(3回めの処理)...

具体的なコードは以下のとおりです。

 

async/await

Promise を使用するとメソッドチェーン形式で非同期処理の実行順を制御できますが、ステートメントとしては一行になります。

この 1 行の長さが処理の数によっては非常に長くなる場合があり、もしかしたら可読性が落ちると感じるかもしれません。

そういった場合には糖衣構文である async/await キーワードを使用して以下のように簡潔に記述することができます。

上記サンプルコードの p_setTimeout 関数は Promise の項で紹介したサンプルコードのものと同様です。

await キーワードを使用して p_setTimeout 関数を 3 回続けて呼び出していますが、その間に以下のように他の構文を記述することもできます。

await p_setTimeout('A',3000);
console.log(‘1’);
await p_setTimeout('B',2000);
console.log(‘2’);
await p_setTimeout('C',1000);
console.log(‘3’);

generator/yield

非同期処理の制御は Promise だけでなく generator/yield を使用しても制御することができます。

ただし、generator も yield も非同期を制御するためにあるのではありません。

generator は名前のとおり「発生させるもの」ですが、なにを発生するのかというと iterator(反復子) です。

iterator は「繰り返すもの」であり、プログラミング言語的には、「処理位置を把握しつつ、コレクションの各要素にアクセス可能なオブジェクト」といったところでしょうか。

generator が生成した iterator の要素を返すのが yield です。

ECMA Script 2015 では function* 構文使用することで関数を generator として宣言することができます。

generator は 2 つ以上の yield を含み、next メソッドによって yield が返す iterator の位置を進めます。
例えば以下のサンプルでは、ブラウザー上に表示された [次へ] ボタンをクリックすると、クリックごとにインクリメントされた数字がブラウザー上に表示されます。

generator として宣言した looper 関数内で while(1) の無限ループ内から yield でループした回数を返していることに注目してください。

また、while(1){} をコメントアウトして、代わりにコメントアウトされている連続した yield  のステートメントを有効にすると looper 関数の返す値が 1 ステートメントずつ進み a, b, c, d とデータが返るのが確認できます。

generator/yield この性質を利用し、以下のようなコードでイベント制御を行うことができます。

仕組みとしては、3 つの steTimeout 関数を generator 内に記述し、yield を使用して iterator とし、steTimeout 関数のイベントハンドラ内に generator の next メソッドを記述して処理が完了してたら iterator を進めるようにしています。

なお、以下のように Promise と組み合わせることもできます。(あんまりメリットを感じませんが)

 

まとめ

今回は JavaScript の非同期処理の制御として、Promise、async/await、generator/yield を使用した方法を紹介しました。

この記事では、汎用性の高い方法として setTimeout を例に紹介しましたが、Web サーバーとのやりとりに限るのであれば、新しくサポートされる Fetch API を使用することができます。

Fetch API は XMLHttpRequest よりも通信について細かい設定が可能なうえ、処理の完了を Promise パターンで返します。

その他、ECMA Script 2016 でサポートされる機能で Async 関数などもありますが、これらについてはあらためて紹介させていただきます。

 

Real Time Analytics

Clicky

Comments (0)

Skip to main content