連載
» 2011年03月23日 00時00分 公開

node.jsでサーバサイドJavaScript開発入門(2):naveでNode.jsのバージョン管理&イベントループ詳説 (2/3)

[森俊夫,@IT]

ECMAScriptとCommonJSについて補足

 前回記事において、「シングルスレッドとノンブロッキングI/Oという特徴をJavaScript自体が持っている」と記載しましたが、間違いです。正しくは、「マルチスレッドとI/O全般をJavaScriptが持っていない」です。

 JavaScriptの言語仕様は、ECMAScriptとして策定されています。ECMAScriptには、スレッドやI/Oに関しては記述されていません。しかし、サーバサイドJavaScriptを作成するうえで、I/O機能は必須であるため、各サーバサイドJavaScriptの実装(RingoJSの前身であるHelmaや、Aptana Jaxerなど)が独自に拡張していました。

 各自が独自に実装していると、API互換性などで問題が出てくるため、「CommonJS」が策定されました。このCommonJSは、サーバサイドJavaScriptだけの仕様ではなく、さまざまなアプリケーションをJavaScriptで作成するための仕様です。

 前回記事では、「Node.jsは、このCommonJSにのっとって開発されています」と記載しましたが、現実的には、CommonJSのごく一部(moduleとassert)を実装しているだけです。Node.jsの特徴である「シングルスレッドでのノンブロッキングI/O」は汎用的な機能ではないためか、CommonJSには定義されていません。

 今後、Node.jsがCommonJSに沿って、実装されていくかは微妙な所ですが、JavaScriptでアプリケーションを作成する際のガイドラインとして、CommonJSは有用です。

もう1度考えたい「イベントループ」とは

 前回記事では、「非同期処理」と「並行処理」を混同している表現がありました。今回は、イベントループのイメージ図とソースコードで説明したいと思います。

イベントループのイメージ図

 Node.jsでは、「並行処理」をシングルスレッドで行うために、「ノンブロッキングI/O」による「非同期処理」を行います。Node.jsでは、イベントループと呼ばれるモデルを採用しています。

 イベントループでは、実行する処理をキューにいったん格納し、順に実行していきます。そのため、シングルスレッドでブロックするI/Oを行った場合、【3】のイベント完了後に【4】のイベントを実行されます。

イベントループのイメージ図

 【3】と【4】を並行処理させるために、ノンブロッキングI/Oを使い、【3】のイベントの完了を待たずに【4】のイベントハンドラを実行させます。【3】のイベントハンドラ完了結果は、コールバック関数を登録することにより、受け取ります。もし、【3】のイベントハンドラ実行が処理をブロックしてしまった場合、【4】のイベントは【3】のブロックが解除されるのを待たされます。

 Node.jsでは、イベントを生成して処理を制御します。例えば、HTTPサーバでクライアントからリクエストを受け取ったときや、ファイルの読み込みが完了したときなどにイベントが、生成されます。

サンプルコードで「イベントループ」を理解する

 次に、実際のコードを例にして説明します。この例は、「index.html」を読み込んで返すだけのWebアプリケーションです。

入口のHTMLを作成

 まず、「index.html」を作成してください。

index.html
<html>
  <head>
    <meta charset="UTF-8">
    <title>index.html</title>
  </head>
  <body>
    index.html
  </body>
</html>

サーバサイドJavaScriptファイルを作成

 次に、以下のコードを「server.js」という名前で保存してください。

server.js
// httpとfsモジュールを読み込む
var http = require('http'),
    fs = require('fs');
 
// httpサーバの作成
http.createServer(function(req, res) {
    console.log("start");
    // index.htmlを読み込んで表示
    fs.readFile('index.html', function(err, content) {
        if (err) { throw err; }
        console.log("response end");
        res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'});
        res.end(content);
    });
    console.log("end");
    // index.htmlの読み込みを待たずにレスポンスを返す
    //res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'});
    //res.end();
}).listen(8192, '127.0.0.1');
console.log('http://127.0.0.1:8192/');

 HTTPのリクエストとレスポンスを示す2つの変数「req」「res」を受け取る関数(イベントハンドラ)をhttp.createServer()に渡して、HTTPサーバを作成します。HTTPリクエスト(イベント)を受け取ると、この関数(イベントハンドラ)をイベントループに登録します。

サンプルの実行

 Node.jsは、イベントキューからイベントを取り出して実行します。Node.jsコンソールから実行します。

$ node server.js
http://127.0.0.1:8192/

 Webブラウザで、「http://127.0.0.1:8192/」を開きます。

Webブラウザの表示

 index.htmlが表示されます。Node.jsを実行したコンソールには、ログが表示されます。

$ node server.js
http://127.0.0.1:8192/
start
end
response end

 index.htmlを読み込む関数である「fs.readFile()」に対して、コールバック関数を渡しています。「fs.readFile()」は、Node.jsにより、非同期で実行され、ファイルの読み込みが完了した時点で、コールバック関数を呼び出すためです。そのため、ログの順番は、「start」「end」「response end」の順になります。

 次ページでは、より深くイベントループを理解するために、ある実験をしてみましょう。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。