[Node.js] イベントループの仕組みを探る 〜 JSおくのほそ道 #002

こんにちはほそ道です。
今回はイベントもといイベントループを取り上げてみます。

[目次はこちら](http://qiita.com/setzz/items/ad9a9c99a4fda041a563)


# イベント駆動処理の作成

とりあえず作ってみよう

## 設計と実装
ほそ道は毎週ボウリングに行くのですが、
自宅やオフィスにボウリングレーンがあったら夢のようだ。。
という夢をかなえる為に **ボウリングレーンを実装** してみました。
ストライクやガターの発生をイベントとして検知したら
ディスプレイになんやかんや表示するという感じのシロモノです。
雰囲気を出す為に`console.log`関数をレーンのディスプレイに見立ててみました。
ちなみに見やすさ重視のためメモリ効率等はいっさい考慮してません。

```js:javascript
var EventEmitter = require('events').EventEmitter;

// define Lane
function Lane01() {
  this.display = console.log;
  this.detect = function(n) {
    this.emit(n.toString() + 'pins');
  };

  // events
  this.on('10pins', function() {
    this.display("Strike! Congratulations!");
  });
  this.on('0pins', function() {
    this.display("Gutter... Don't mind...");
  });
}
Lane01.prototype = new EventEmitter();

// Let's play!
var lane01 = new Lane01();
lane01.detect(10);
lane01.detect(0);
console.log("Thanks to Play!");
```

## 解説
`this.display = console.log;`で関数の参照を渡してますが
出力をレーンのディスプレイに見立てるというモデリング的なものなので軽く流してください。。
`EventEmitter`というイベントを実装する為の機能を取り込みます。
これをボウリングレーンのprototypeに突っ込んでレーンをイベント駆動オブジェクトとします。
`インスタンス.on('イベント名', コールバック)`としてやればイベントが実装されます。
`インスタンス.emit('イベント名')`でイベントが発生します。
さて、出力結果を見てみましょう。

```text:出力結果
Strike! Congratulations!
Gutter... Don't mind...
Thanks to Play!
```

うん、いいね！

。。。おや。
そりゃそうだよな、と思いつつちょっとだけシュン。。
**イベント実装しただけだと非同期処理になるわけじゃないんだね。。**
最終行に書いた出力「Thanks to Play!」はふっつーに最後に出てますね。。
[前回も](http://qiita.com/setzz/items/16597eaabb1226975c51)取り上げましたがI/Oは非同期+ワーカスレッド実行にした方がグッドパフォーマンスな訳で。
これだとたくさんの人がプレーするとディスプレイもレーンの検知も遅延しちゃうかもしれませんね。。

# イベント駆動非同期処理の作成

というわけで作ってみよう

## 改良型レーンの実装+出力結果

実装してみました。出力結果まで合わせて見てみましょう。

```js:javascript
var EventEmitter = require('events').EventEmitter;

// define Lane
function Lane02() {
  this.display = console.log;
  this.detect = function(n) {
    this.emit(n.toString() + 'pins');
  };

  // events
  this.on('10pins', function() {
    process.nextTick(this.display.bind(this, "Strike! Congratulations!"));
  });
  this.on('0pins', function() {
    process.nextTick(this.display.bind(this, "Gutter... Don't mind..."));
  });
}
Lane02.prototype = new EventEmitter();

// Let's play!
var lane02 = new Lane02();
lane02.detect(10);
lane02.detect(0);
console.log("Thanks to Play!");
```

```text:出力結果
Thanks to Play!
Strike! Congratulations!
Gutter... Don't mind...
```

「Thanks to Play!」が最初に出ている！
というわけで **非同期(ノンブロッキング)イベント型レーン** に改良されました！
最初にサンクスメッセージが出るので
なんとなく「帰れ！」と言われてるみたいな感じがするツンデレレーンですが。
たくさんの人が投げても遅延しにくくなっているはず！(少なくとも前よりは)

## コード解説
ほとんど１番レーンと変化はありません。
イベントのコールバックの処理を
`process.nextTick(this.display.bind(this, "Strike! Congratulations!"));`
に変えただけです。
displayの引数を`bind`で渡しているのは単なる調整です。
大事なのは、そう、`process.nextTick`なんです！

# イベントループ！

## Node.jsのイベントループ
「大体こんな感じじゃね？」みたいな部分も結構ありますが力強く説明してみます。

```text
Node.jsのメインスレッドは起動すると、記述コードの実行よりも先に
イベント検出/実行をコントロールする「イベントループ」を起動します。

そして「イベントキュー」というヤツがいて、こいつにイベントが投入されます。
イベントキューにコールバック関数を突っ込むのが先ほど登場した`process.nextTick`！

イベントは「イベントキュー」内のキューとして取り出されます。

イベントループは超短い周期でぐるぐる回り、
イベントが検出でき、メインスレッドのタスクがなくなったら
FIFOでキュー(コールバック)を取り出してコールバックを実行です！
```

すなわち
こんな感じになっとるはずです。(余計わかりにくいかもしれない)
![eventloop.png](https://qiita-image-store.s3.amazonaws.com/0/39330/046e60dc-42b9-1751-ac20-dd1d5d4d190a.png)

## ブラウザもイベントループしてるよね
ちなみにブラウザジャバスクリプタの皆様もイベントループといったら
FireFoxにはタイマースレッドってヤツがいて、みたいな話や
`addEventListener`や`setTimeout`なんかでおなじみだと思います。
ほそ道は基本的にはあの感じをイメージしてます。
じつはNode.jsでも`setTimeout(callback, 0)`みたいな事をすれば
`process.nextTick`と同じ動きになります。
しかし、ちゃんとイベントループ設計に乗っかって実装されているのは`process.nextTick`の方で
パフォーマンスは雲泥の差らしいです。

はい。
今回は以上でございます！
御付き合い頂き有り難う御座いました！

# 非同期/ノンブロッキングなトピックのまとめ
今回、JS野郎のたしなみとしてNodeのベースとなるアーキテクチャに触れてみました。
ハイパフォーマンス実現に向けた設計思想がとんがりつつ明確で、
本格的にモノ造りするときはブロックが発生してないかや、どこをコールバックとすべきかなど、
思想に沿っているかを意識しつつモデリングしていく事が重要なポイントになりそうですね。
ブラウザJSにズブズブだったほそ道にとっては共通点・違い含めて非常に新鮮です。


[次回](http://qiita.com/setzz/private/149b28c89a012ead26aa)は無視し続けてきた予約語`require`。モジュールのお話をします！