目次


Node.js のイベント・ループを効果的に使用する

Node.js アプリ内で予期しない結果を招かないようにする方法

Comments

駆け出しの Node.js アプリケーション開発者の学習曲線の一部となるのは、シングルスレッド・イベント・ループがどのように機能するのか、そしてどのような場合にこのイベント・ループが予期しない結果を招くのかを十分に理解することです。このチュートリアルでは、3 つのインタラクティブなサンプルを通じて、イベント・ループの使用方法を実践できます。すぐに、非同期呼び出しに容易に対処できる高速で効率的なコードを作成できるようになるはずです。

イベント・ループがどのように機能するのかを説明する、3 つの単純なコードを見てみましょう。

サンプル 1: 単純な例

挑戦アイコン 最初のサンプルでは、3 つの関数を定義して、その 3 つの関数を呼び出します。「実行」をクリックしてコードを実行してください。次に、setTimeout() 呼び出しに含まれる数値を変更して、出力がどのように変わるのかを見てみます。例えば、すべての値を 0 に設定してください。

Show result

元のサンプルは以下の出力を生成するように思えます。

world!
Hello
there,

けれども、実際にはそうなりません。printNow('Hello') が最初に実行され、次に printSoon('there,') が実行され、最後に printEventually('world!') が実行されることになります。そのわけを説明すると、Node エンジンは printEventually() 呼び出しに含まれる setTimeout() の値を認識すると、この呼び出しを OS に渡し、次の printNow() の呼び出しに取り掛かります。このコードは同期実行されるため、メッセージ Hello がすぐに出力されます。最後に、printSoon() に含まれる setTimeout() の呼び出しが OS に渡されます。その 1 秒後に (printSoon() は 1000 ミリ秒待機します)、printSoon() に含まれる setTimeout() タイマーが満了するため、メッセージ there がコンソールに出力されます。それから 1 秒経つと、最後の setTimeout() が満了してメッセージ world! が出力されるというわけです。したがって、3 つのステートメントは以下の順序で処理されます。

Hello
there, 
world!

イベント・ループの仕組み

従来型の Web サーバーはマルチスレッド化され、通常はセッションごとに固有のスレッドが割り振られます。この手法は功を奏するとは言え、セッションがアイドルになっている間、使用されていないリソースを Web サーバーが割り振らなければなりません。これらのアイドル・セッションのオーバーヘッドが原因で、需要の急増に対処するためにサーバーをスケーリングするのが難しくなります。

一方、Node エンジンが使用するのは単一のスレッドです。この単一のスレッドが、何らかの処理に対応できる状態になっているというオペレーティング・システムからのすべての通知に対処します。その何か (例えば、データベースまたは REST インターフェースに対する呼び出し) が非同期で行われるものであれば、Node エンジンはオペレーティング・システムに対し、呼び出しを処理できる状態になった時点 (例えば、データベースまたは REST 呼び出しからのデータが到着した時点) で通知するよう指示します。それまでの間、Node イベント・ループは完了しなければならない次の処理に移ります。

理解しておかなければならない点は、Node エンジンはあらゆる要求をただちに処理することです。この「ただちに」という言葉は、処理できる状態になった時点で通知するようオペレーティング・システムに指示することを意味する場合もあります。この素晴らしい仕組みについて詳しくは、このチュートリアルの末尾にある「関連トピック」セクションに一覧表示されている動画を見てください。

サンプル 2: コールバック・パターン

最初のサンプルでは Node が非同期コードを処理する方法を説明しましたが、通常、非同期コードを呼び出すにはコールバック・パターンを使用します。コールバック・パターンは以下のような形になります。

リスト 1. 非同期関数を定義する疑似コード
function asyncCode(arg1, arg2, callback) {

  // Do whatever the function does here. When it's complete, it should 
  // invoke the callback function, returning a (hopefully null)  
  // JavaScript Error object and the results of this function. 

  return callback(error, results);
}

asyncCode() に渡される最後のパラメーターは別の関数です。asyncCode() は処理を完了すると、渡されたコールバック関数を呼び出します。慣例により、非同期関数はコールバックに最初のパラメーターとして JavaScript Error オブジェクトを渡し、続いてその非同期関数が生成した結果を渡します。asyncCode() 関数では必要な数だけ引数を使えること、そして必要な数だけ引数をコールバック関数に渡せることに注意してください。

以上が、非同期関数を定義する方法です。非同期関数を呼び出すには、以下のようにします。

リスト 2. 非同期関数を呼び出す疑似コード
asyncCode(x, 37, function(error, results) {
  if (error != null)
    // Handle the error if there is one
  else
    // Otherwise do whatever we want with the results
});

挑戦アイコン コードのこのバージョンでは、コールバック関数を使用しています。このままの状態で、「実行」をクリックしてコードを実行してください。その後、printMessage() 呼び出しに含まれる数値を変更して、出力がどのように変わるのかを見てみます。console.log('Hello') を別の printMessage() 呼び出しで置き換えてみてください。timeout の値が 0 だとしたら、どうなるでしょうか。実行順序に影響すると思いますか?

Show result

上記の printMessage() 関数は、コールバック・パターンを実装しています。この関数は timeout を設定しているため、Node はその timeout を OS に渡した後、次の処理に移ります。この例での次の処理は、単純な console.log() 呼び出しです。その呼び出しに続いて printMessage() の別の呼び出しが行われて、別の timeout が設定されます。timeout が満了するとコードが完了し、メッセージ world! がコンソールに書き込まれます。つまり、このコールバック関数は最初のサンプルと同じメッセージを生成するということです。

Hello
there, 
world!

サンプル 3: コールバックのネスト

何らかの理由で 3 つの単語からなるメッセージを特定の順序で出力しなければならない場合は、コールバック関数をネストする必要があります。例えば、timeout パラメーターの値として 0 から 5000 までの数値が無作為に生成されるとしたら、メッセージがどのように出力されるかを知る由はありません。

挑戦アイコン「実行」をクリックして、このままの状態でコードを実行してください。次に、printMessage() 呼び出しに含まれる数値を変更してみてください。どの値を使用するとしても、コードは同じ順序で実行されます。

Show result

このコードは、printMessage() が特定の順序で呼び出されるようにしています。最初の printMessage() 呼び出しが渡すコールバック関数は、同じく printMessage() を呼び出して別のコールバック関数を呼び出します。その別のコールバック関数が printMessage() を呼び出します。このコードによって、以下の意味不明なあいさつ文が生成されます。

world!
Hello
there,

ここではエラーの処理を省略して printMessage() を再び呼び出す前のコードを 1 行にしているので、このコードは比較的読みやすくなっています。エラー処理をコードに戻し、呼び出しの間に複雑なロジックを追加したとしたら、たちまちのうちにコールバック地獄に陥り、コードが複数のレベルにネストされて理解するのが難しくなります。コールバック地獄について詳しくは、「関連トピック」セクションを参照してください。

まとめ

このチュートリアルでは、Node.js のシングルスレッド・イベント・ループについて簡単に説明しました。データベース、ファイルなどには Node ライブラリーを使用してアクセスするため、非同期メソッドの処理方法 (そしてコードが特定の順序で実行されるようにする方法) を把握していることが不可欠のスキルとなります。イベント・ループを十分に理解していれば、非同期呼び出しを簡単に処理できる、高速で効率的なコードを作成できるようになります。

このチュートリアルのインタラクティブなサンプルについて

このチュートリアルのインタラクティブなサンプルで使用している developerWorks サンドボックスは、IBM Bluemix 上で稼働する OpenWhisk 環境にデプロイされています。今後数週間、数か月間にわたって追加されるさまざまなインタラクティブなコンテンツをお見逃しなく (いつもながら、無料の Bluemix アカウントに登録して IBM クラウド・プラットフォームの機能を調べることをお勧めします)。

謝辞

このチュートリアルの元のバージョンにあったエラーと誤解を正してくれた IBM の Sam Roberts に感謝の言葉を贈ります。他にまだ誤りがあるとしたら、すべて著者のみの落ち度です。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development
ArticleID=1046508
ArticleTitle=Node.js のイベント・ループを効果的に使用する
publish-date=06012017