はじめてのNode.js:Node.jsのイベントシステムを知る 2ページ

さまざまな機能が実装された「モジュール」を使う

 Node.jsにはさまざまな機能が実装された標準ライブラリが用意されており、これらは機能ごとに「モジュール」にまとめられている。

モジュールを読み込む——require関数

 モジュールを読み込んで利用可能にするにはrequire関数を使用する。

require(moduleName)

 moduleName引数には、ロードしたいモジュールの名前を文字列で指定する。require関数は指定されたモジュールを読み込み、そのモジュールに含まれるメソッドやクラスといったコンテンツを格納したオブジェクトを返す。require関数を実行したプログラムからは、このオブジェクトを通じてモジュール内の関数やクラスなどにアクセスできる。

 たとえば「http」というモジュールを利用するには、次のようにする。

var http = require('http');

 なお、REPL内ではrequire関数を使用せずとも、モジュール名を入力するだけでそのモジュールをロードできる。たとえばREPL内では「http = require(‘http’)」とする代わりに次のようにしてhttpモジュールをロードできる。

> http

 読み込んだモジュール内の関数を呼び出すには、.演算子を使用する。たとえばhttpモジュールに含まれるcreateServer関数を実行するには次のようにする。

var server = http.createServer();

 createServer関数はHTTPサーバー(http.Server)クラスのインスタンスを作成する関数で、戻り値としてhttp.Server型のオブジェクトを返す。

Node.jsのコアモジュール

 Node.jsに付属しているモジュールは「コアモジュール」と呼ばれている。Node.js 0.8系では、表1のコアモジュールが用意されている。

表1 Node.js 0.8系のコアモジュール
モジュール名提供される機能Stability(安定度)
assertアサーション5
bufferバイト列の格納および操作3
child_process子プロセスの生成や管理3
cluster複数のプロセスを使った負荷分散1
consoleコンソールへのメッセージ出力4
crypto暗号化/ハッシュ2
dgramUDPを扱うソケット関連の処理3
dnsDNS関連の処理3
domain複数のIO処理間の連携1
eventsイベント処理を実装するための基底クラス4
fsファイルおよびファイルシステムの操作3
httpHTTPサーバー/クライアント3
httpsHTTPSサーバー/クライアント3
netソケットの操作3
osOSに関連する情報の取得4
pathパス文字列の処理3
punycodePunycode文字列のエンコード/デコード2
querystringHTTPで使われるクエリ文字列の処理3
readline標準入出力を使用した対話的インターフェイス2
replREPL(なし)
streamストリーム入出力処理が定義された基底クラス2
string_decoderバイナリ列から文字列へのデコード3
tlsOpenSSLを使ったTLS/SSL通信3
ttyTTY(キャラクタ端末)の情報取得2
urlURL文字列のパースやフォーマット3
util各種ユーティリティ関数5
vmJavaScriptの実行エンジン(仮想マシン、VM)2
zlibzlibを使ったデータの圧縮/伸張機能3

 コアモジュールの詳細については、Node.jsのドキュメント(http://nodejs.org/api/)で参照できる。なお、「Stability(安定度)」というのは、その名のとおりそのモジュールがどのくらい安定しているかの完成度を表すもので、1~5の5段階が用意されている(表2)。ただ、安定度が低いからといって使い物にならないというわけではなく、Unstableだからといって使用を控えるべき、ということではない。

表2 Stabilityレベル
レベル説明
1Experimental(実験的実装)
2Unstable(不安定)
3Stable(安定)
4API Frozen(今後APIの変更は行われない)
5Locked(今後変更は行われない)

 また、Node.jsではJavaScriptエンジンとして使用している「V8」がサポートしているECMAScript 5で定義されている関数やクラスに加え、いくつかのクラスや関数、オブジェクト、関数が追加されている(表3)。これらは特定のモジュールをロードすることなしに利用できる。

表3 Node.jsでグローバルに定義されているクラスや関数、オブジェクト
クラス/関数/オブジェクト名説明/処理内容
Bufferクラスバイト列を扱うクラス
setTimeout関数指定した時間が経過したときに指定した関数を実行する
clearTimeout関数setTimeoutで設定した関数の実行を取り消す
setInterval関数指定した一定間隔で指定した関数を実行する
clearInterval関数clearInterval関数で設定した関数の実行を取り消す
require関数モジュールをロードする
moduleオブジェクトモジュール情報にアクセスするためのオブジェクト
processオブジェクトプロセス情報にアクセスするためのオブジェクト

 モジュールを自作したり、サードパーティが公開しているモジュールを利用することも可能だ。

ECMAScriptとは

 JavaScriptを標準化するための国際規格。1999年に策定されたECMAScript 3が長らく標準規格として使われていたが、2009年にさまざまな拡張が行われたECMAScript 5がリリースされた。ECMAScriptではJSONデータを扱うJSONクラスの追加やObjectクラスの拡張などが行われている。

非同期処理とコールバック関数——synchronusとasynchronous

 Node.jsの関数には、「同期的(synchronous)な関数」と、「非同期的(asynchronous)な関数」がある。呼び出された際、その処理の実行が完了するまでは関数の呼び出し元には戻らない関数のことを同期的な関数と呼び、console.log関数などがこれに該当する。いっぽう、呼び出された際、関数内で実行すべき処理が完了する前に呼び出し元に戻る関数のことを「非同期的な関数」と呼ぶ。リスト3-1の最後で呼び出しているserver.listenメソッドも非同期的な関数である。このメソッドはHTTPサーバーの待ち受けを開始させるもので、この関数自体はすぐに終了するが、バックグラウンドでは待ち受け処理が継続して実行されている。

 Node.jsプログラムでは、このような「非同期的な関数」が多用されるのが特徴だ。そして非同期的な関数では、関数が処理を完了する前に戻り値を返すため、関数の戻り値でその結果を受け取ることができない。そのため、非同期的な関数や非同期的なメソッドを持つクラスには「コールバック」や「イベント」といった処理結果を通知するための機構が用意されている。コールバックは、関数の引数として「コールバック関数」と呼ばれる別の関数を指定しておき、処理の完了時にその結果をコールバック関数の引数として与えて実行する仕組みだ。

 たとえばファイルからその内容を読み出す、といった処理を考えてみよう。Node.jsには、ファイルに対する操作をまとめたfsモジュールが用意されており、ファイルからその内容を読み出すにはfsモジュールに含まれるreadFile関数が利用できる。

fs.readFile(filename, [encoding], [callback])

 readFile関数のfilename引数にはファイル名を、encoding引数には読み出すデータのエンコーディングを、第3引数にコールバック関数を指定する。

 readFile関数は呼び出すと即座にundefinedを返し、バックグラウンドでファイルからの読み出しを実行する。そしてデータの読み出しが完了し、スレッドがアイドル状態になったタイミングで指定したコールバック関数が実行される。このとき、コールバック関数には(err, data)という引数が与えられる。err引数には処理が成功した場合nullが、失敗した場合にはエラーに関する情報が格納されたエラーオブジェクトが与えられる。また、data引数には読み出したデータが与えられる。

 REPLを使って実際の挙動を確かめたものが以下の実行例となる。ここでは「/etc/passwd」というファイルを読み出し、読み出し完了後に「read!」というメッセージと、読み出したファイルの内容を表示させている。

> var fs = require('fs');
undefined
> function test01() {
... fs.readFile('/etc/passwd', 'utf8', function (err, data) {
..... console.log('read!');
..... console.log(data);
..... });
... console.log('foo');
... console.log('bar');
... for (var i = 0; i < 10000; i++) {
..... console.log('.');
..... }
... }
undefined
> test01()
foo
bar
.
.
 :
 :
.
.
undefined
> read!
##
# User Database
 :
 :(/etc/passwdファイルの内容が表示される)
 :

 ここで注目してほしいのが、fs.readFile関数の実行後、「console.log(‘foo’)」や「console.log(‘bar’)」、そしてforループ内の処理が完了してからコールバック関数内に記述されている処理が実行されている点だ。/etc/passwdファイルのファイルサイズは数キロバイト程度でありファイルの読み込み自体はほぼ一瞬で完了するにも関わらず、readFile関数に続く処理が完了するまでコールバック関数は実行されない。このような挙動となるのは、Node.jsが基本的にシングルスレッドでプログラムを実行しているためだ。この挙動を図示すると、図3のようになる。

図3 Node.jsにおけるプログラム実行の流れ
図3 Node.jsにおけるプログラム実行の流れ

 Node.jsでは実行すべき関数をキューのようなもので管理しており、実行している処理が終了してアイドル状態となった時点で、キュー内に入っている次の関数を実行するようになっている。そのため、非同期な関数によってバックグラウンドで行われている処理が完了しても、別の関数が実行されている間は対応するコールバック関数は実行されない。言い換えれば、原則として複数の関数が並列として実行されるという状況は発生しないのである。そのため、複数の関数が同時に同じ変数にアクセスすることによる「競合」やロックなどを気にする必要はない。

 Node.jsではreadFile関数以外にもコールバックを用いる関数が多く用意されている。その場合もreadFile関数と同様、呼び出されるコールバック関数の第1引数にはエラーオブジェクトが、第2引数以降には処理結果を含むオブジェクトが与えられるのが一般的だ。