Node.js API - JavaScript のタイマー機能と非同期呼び出し
今回は Node.js API を使ったタイマー機能とそれに関連する関数の非同期呼び出しについて説明します。
Node.js API にある機能ですが、もともと JavaScript にもある機能なので、ブラウザーが対応していればクライアントサイドでも利用できます。
クライアントサイドで使う場合は window. をつけたものに読み替えてください。(例 : setTimeout → window.setTimeout)
タイマーの設定
setTimeout
タイマー機能で最も基本的な関数は setTimeout() です。 これは指定した時間がたった後に関数を実行します。setTimeout(func, delay [, arg0, arg1, ...])delay が指定する時間で ミリ秒 で設定します。 以下の例では 5 秒後にログを出力します。
setTimeout(() => { console.log('Timeout'); }, 5000);実行する関数が引数をとる場合には delay の後に引数を渡します。
setTimeout((a, b) => { console.log('Timeout', a, b); // Timeout foo bar }, 5000, 'foo', 'bar');ただ、無名関数を使う場合はクロージャーの機能で変数を持たせることができるので、使う必要はないです。
setInterval
setTimeout() では渡した関数を一回実行して終わりですが、 setInterval() の場合は指定した間隔で繰り返し呼び出します。 それ以外は setTimeout() と同じです。setInterval(func, delay [, arg0, arg1, ...])以下の例では 1 秒間隔でログを出力します。 ただ、これを本当に実行すると止まらなくなります。
setInterval(() => { console.log('Interval'); }, 1000);
タイマーの解除
設定したタイマーを解除する場合、各関数に対応した clearXxxx() の関数を呼び出します。clearTimeout(timeoutID) clearInterval(intervalID)解除関数にはタイマーの ID となるオブジェクトを渡す必要があります。 これは各設定関数の戻り値です。
前章のサンプルでは setInterval() の関数は止まりませんでしたが、 次の例では 5 秒後に止まります。
var intervalID = setInterval(() => { console.log('Interval'); }, 1000); setTimeout(() => { clearInterval(intervalID); }, 5000);ちなみに、無名関数を使わずに短く書くと次のようになります。
setTimeout(clearInterval, 5000, setInterval(console.log, 1000, 'Interval'));
非同期呼び出し setImmediate
setImmediate() は指定した関数をすぐに(Immediately) 実行します。setImmediate(func, [, arg0, arg1, ...])以下の例ではすぐにログを出力します。
setImmediate(()=>console.log('Immediate'));setImmediate() は setTimeout で 0 秒を設定したものに相当します。 ただ、相当はするのですが、 setTimeout(..., 0) と同じでもありません。
setTimeout() には制限があって、最小遅延(4,5ns 程度)が必要なので、すぐに実行したい場合には setImmediate() の方を使います。
タイムアウト時間を設定しない setImmediate() はなぜ必要なのでしょうか?
これは "すぐ" といっても、本当に "すぐ" ではないところがポイントです。
setImmediate() を実行すると、すぐに処理を返します。ここは本当にすぐです。その後、一連の処理が終わってから、指定した関数を実行します。 つまり、 関数を非同期に呼び出すことになります。
例えば、ボタンなどをクリックした後の処理ではすぐに処理を返さないと固まってしまいます。 そこで時間のかかる処理を行うことはユーザビリティーを悪化させます。
そこで、実行時間の長い処理は setImmediate() に設定して、一旦処理を返します。 ブラウザーはレイアウトや描画などの未処理の作業を処理してから、その時間のかかる処理を実行するようになります。
サンプルコード
記事で使用したサンプルコードは以下のリンクから取得(名前をつけて保存)できます。 これを node コマンドの引数に渡して実行します。$ node timer.js
Node.js API (events) - イベントシステム
Node.js の API の多くは非同期のイベント駆動構造で作成されてます。
その機能は標準モジュールの "events"
が提供しています。
今回は events モジュールのイベント駆動のシステムについて解説します。
イベント駆動の基本
Node.js では「 emitter 」(エミッター、発行者) と呼ばれるオブジェクトが用意されており、 これに イベントに関連付けて関数を登録しておくと、イベントが発生した時にその関数が実行されます。 一般的にはこの関数はコールバック関数と呼ばれますが、 Node.js では 「 listener 」(リスナー、傾聴者) と呼んでいます。例えば、 net.Socket オブジェクトではデータを受信すると
"data"
というイベントを発行します。
そのデータの受信処理の関数を登録して、受信時に呼ばれるようにします。エミッターは EventEmitter クラスのインスタンスです。 自分でエミッターを作りたい場合は EventEmitter を継承して作成します。
以下のサンプルでは ES6 の継承機能を使ってますが、 Node.js では
util.inherits
という関数も用意されています。
"use strict"; const EventEmitter = require('events'); // 継承して emitter を作成 class MyEmitter extends EventEmitter {}; var myEmitter = new MyEmitter();リスナーの登録は EventEmitter の on メソッドにイベント名とリスナーを渡します。 イベント名はキャメルケースでつけるのが慣例です。
// listener の登録 function barListener() { console.log('a foo event occurred!'); } myEmitter.on('fooEvent', barListener);イベントを発行する(イベントを発生させる)には イベント名を引数として emit メソッドを実行します。
// イベントの発行 myEmitter.emit('fooEvent'); // barListener の実行 => a foo event occurred!リスナーを実行させようとすることを call (呼び出し) ではなく、 invoke (呼びかけ) といいます。
上記は on() メソッドで登録しましたが、 once() でも登録することができます。ただ、この場合は何度イベントを発行しても、リスナーは最初の一度しか実行されません。また、後述するように非同期で実行させることもあります。
このようにリスナーは状況により実行されなかったり、後回しになったりするので、 「呼びかけ」であり、コールバックではなく「リスナー」なのではないかと思います。
リスナー
引数
イベント発生時の情報を処理側であるリスナーに渡したい場合には引数を使います。var myEmitter = new MyEmitter(); myEmitter.on('fooEvent', function(a, b) { console.log('fooEvent', a, b); }); myEmitter.emit('fooEvent', 'bar', 'baz'); // fooEvent bar baz
this
JavaScript のややこしいところに this
は何を指しているかという問題があります。リスナー実行時の
this
はイベントを発行したオブジェクトに書き換えられています。
var myEmitter = new MyEmitter(); myEmitter.on('fooEvent', function() { console.log('fooEvent this =', this); }); myEmitter.emit('fooEvent'); // fooEvent this= MyEmitter { // domain: null, // _events: { fooEvent: [Function] }, // _eventsCount: 1, // _maxListeners: undefined }ES6 のアロー関数を使うと
this
は守られます。
var myEmitter = new MyEmitter(); myEmitter.on('fooEvent', () => { console.log('fooEvent this = ', this); }); console.log('this = ', this); // this = {} myEmitter.emit('fooEvent'); // fooEvent this = {}
同期と非同期
一つのイベントに対して複数のリスナーを登録することができ、 リスナーはイベントに登録した順に実行されます。リスナーの実行はイベントが発行されるとすぐに行われる同期処理です。
var myEmitter = new MyEmitter(); myEmitter.on('fooEvent', () => console.log('1')); myEmitter.on('fooEvent', () => console.log('2')); myEmitter.on('fooEvent', () => console.log('3')); myEmitter.emit('fooEvent'); // 1 // 2 // 3イベントの発生と処理の実行を非同期にしたい場合は setImmediate() か process.nextTick() を使います。
var myEmitter = new MyEmitter(); myEmitter.on('fooEvent', () => console.log('1')); myEmitter.on('fooEvent', () => { setImmediate(() => console.log('2 (async)')); }); myEmitter.on('fooEvent', () => console.log('3')); myEmitter.emit('fooEvent'); // 1 // 3 // 2 (async)非同期にするための関数が setImmediate(Imediate : 直接の、即時の)というのはちょっと変に感じるかもしれませんが、 これは setTimeout の待ち時間 0 という意味での Immediate です。 また setImmediate() と process.nextTick() は微妙に違うらしいです。
エラーイベント
用意するイベントとそのイベント名はエミッターを作る側で自由に付けられます。 ただ、エラーが発生した場合には "error" イベントを発生させ、エラーオブジェクトを渡すのが慣例になっています。このエラーイベントが発生し、何もしていないと Node.js はエラーで落ちます。
var myEmitter = new MyEmitter(); myEmitter.emit('error', new Error('whoops!')); // events.js:141 // throw er; // Unhandled 'error' event // ^ // Error: whoops! // at Object.<anonymous> (d:h...エミッターを使う側は "error" イベントにはエラー処理のリスナーを必ず登録するのが、 ベストプラクティスとなっています。
var myEmitter = new MyEmitter(); myEmitter.on('error', (err) => { console.log('myEmitter error happned. ', err); }); myEmitter.emit('error', new Error('whoops!')); // myEmitter error happned. [Error: whoops!]
サンプルコード
記事で使用したサンプルコードは以下のリンクから取得(名前をつけて保存)できます。 これを node コマンドの引数に渡して実行します。$ node events.jsnode コマンドの使い方について詳しくは以前の記事をご覧ください。
Node.js API (process) - exit によるプログラムの終了
プログラミング言語ではたいていプログラムの終了用に exit が用意されています。
Node.js でも Node.js API として exit があります。
今回は exit 関連の機能を紹介します。
プログラムの終了
終了ステータス
通常、 exit を使わなくても、 Node.js の起動時に指定したスクリプトの最後まで達すれば、プログラムは終了します。 このときの終了ステータス(exit code) は 0 です。JavaScript のコードのエラーなどで Node.js がエラー終了した場合にはエラーの内容に対応した終了ステータスで終了します。 Node.js のエラーでなくとも、プログラムとして正常終了ではない場合は 0 以外の終了ステータスで終わるべきです。
exit は途中で終わるためだけでなく、適切な終了ステータスを設定するために exit は必要です。
実際に終了ステータスの値をどうするか というのはプログラム側で決める必要があります。 ただ、特に細かく決めたりしない場合は、正常終了でなければ 1 にしておけばいいと思います。
終了ステータスの仕様等について詳しく知りたい方は以下の記事の「終了ステータス」の節をみてください。
process.exit
Node.js の exit はグローバルオブジェクトである process のメソッドとして用意されています。引数に渡した値を終了ステータスとしてプログラムを終了します。
process.exit(1);
引数を省略した場合のデフォルト値は 0 です。
終了イベント
exit 関連のイベントについて説明します。 イベントシステムの基本については以前の記事を見てください。exit イベント
プログラムの終了前に呼ばれるイベントです。 リスナーの関数には終了ステータスが引数として渡されます。process.on('exit', (code)=> { console.log('Program exit. code = ', code); });基本的に終了状態のチェックなどに使うもので、 ここから非同期処理を実行して、終了を回避させるといったことはできません。
例えば、以下の例ではログは出力されません。
process.on('exit', (code)=> { // 非同期は実行されない setImmediate(console.log, 'This will not run'); });
beforeExit イベント
beforeExit イベントはすべてのイベントループやスケジュールされた処理が終わり、終了される前に発生します。 こちらはリスナーの関数から非同期処理を実行することもできます。ただ、スクリプトが最後までいって終了するときに発生するため、 process.exit で終了した場合にはこのイベントは発生しません。
process.once('beforeExit', () => { console.log("before exit"); });なお、上記の例で once() を使っているのは on() ではリスナーが繰り返し呼ばれて、終了しなくなるためです。
サンプルコード
記事で使用したサンプルコードは以下のリンクから取得(名前をつけて保存)できます。 これを node コマンドの引数に渡して実行します。$ node process_exit.jsnode コマンドの使い方について詳しくは以前の記事をご覧ください。