記述内容
このページは非同期処理と同期処理の概要とJavaScriptの新しい機能であるジェネレータとyield(イールド)について記述しています。
非同期処理と同期処理の概要
複数の処理をするとき、動作の処理完了を待って次の処理に移行する同期処理と動作完了を待たずに次の処理を実行する非同期処理があります。
どちらが優れているというものではなく、場合に応じて使い分けることが肝要です。CやCOBOLなどにおいて同期処理が多く、JavaやJavaScript等ではイベントの発生により制御する処理が通例で、非同期タイプが一般的です。
同期処理はAPIやFunctionと呼ばれる関数に処理を依頼すると動作が完了するまで制御が戻ってきません。処理を順に上から並べれば動作することが多いです。一方、非同期処理は動作が完了しないうちに制御が戻ってきます。
上の例では、①の処理を非同期に処理して2秒間の時間短縮を試みていますが、厳密には③において①の動作完了を確認する処理を追加しなければなりません。
大きなファイルを読み込むにはかなりの処理時間を要します。読み込み完了までの間に、読み込みデータに関わらない処理ならば、読み込みが終わるまで他の処理をすることができます。このことを並行処理といいます。
読み込みデータを参照する処理では、読み込み完了を待たなければなりません。このとき、システムが用意した適切な関数ならばWindowsなどのOSが待ち時間を有効利用します。
自前で作ったウエイト処理ではコンピュータ資源を無駄遣いし、次の処理を待つ間にキー入力や電子メール受信を受付けられない状態になることがあります。
そのため、同期処理はシステムが準備した適切なAPIを使うことが決め手になりますが、JavaScriptではまだ多くは準備されていません。以下にジェネレータによる待機処理例を掲げます。
ジェネレータとyield
画像描画は一定時間、表示しないと認識されません。画像を切り替えるには、setTimeoutなどの時間関数を使うことが多いです。今回はジェネレータ関数を用いて同期処理に近いことをしてみます。
15行のfunction*でジェネレータ関数を定義します。そして、処理をいったん停止する箇所にyieldを定義します。12行でジェネレータ関数のオブジェクトデータを取得し、ジェネレータを作成します。console.logのメッセージでも明らかなように、ジェネレータ関数は最後まで実行されず、非同期に制御は戻ってきます。
一方、ジェネレータ関数ではyield n=0;を実行して待機状態になります。待機状態はnext()関数が実行されると解除します。imageDrwa(0)が画像描画関数であり、十分な描画時間が約束されます。
次に、ジェネレータを作成する前に設定したsleep()関数が1秒経過して起動し、next()関数が実行されると次のyield n=1;までが実行し待機状態になって、2番目の画像が描画されます。
同様に、1秒経過して3番目の画像が描画され、タイマーを停止させて動作状態を表示します。表示結果からも明らかなように、実行時間は各区間でおよそ1000msであり、一応、仕様どおり動作していることが判ります。
動作結果
ソースコード
<html> <head> <script type="text/javascript" charset="Shift_JIS"> var n=0; var tm = []; var first; var timerID; var g; timerID = setInterval("sleep()", 1000); g = genfunc(10); // ジェネレータを作る console.log("Processed."); function* genfunc(e){ // ジェネレータ関数 console.log("G="+g+" timeID="+timerID); first = new Date().getTime(); imageDraw(0); yield n=0; imageDraw(1); yield n=1; imageDraw(2); yield n=2; clearInterval(timerID); for(i=0; i<=n; ++i){ console.log( "i="+i+" 実行時間="+tm[i]+"<BR>" ); document.write( "i="+i+" 実行時間="+tm[i]+"<BR>" ); } //javascript_die(); // FINISH } function sleep(){ // 1000ms g.next(); // "png 0~2" } function imageDraw(l) { //画像表示 var g="<img style='border: 1px solid red;' src='img/SightSeen" +l+".png'"+" width='600' height='250';/>"; document.getElementById("expl").innerHTML = g; tm[l]=new Date().getTime() - first; // 実行時間を計測 } </script> </head> <div id="expl"></div> </html>
参照資料
①generatorとJavaScriptの非同期処理
②ES6 Generatorを使ってasync/awaitを実装するメモ
最後に
JavaScriptにおける同期処理を試してみました。ここで示したコードは画像ファイル(SightSeen0.png~SightSeen2.png)をきちんと用意すれば、ブラウザChromeを使ってローカル環境でも動作しました。待機関数、同期処理は奥が深いです。