Node.jsの勉強（３）events

Node.jsの勉強3回目はeventsモジュールについてです。

eventsモジュールには唯一`EventEmitter`というイベントを処理するためのコンストラクタが定義されています（`EventEmitter`コンストラクタ以外にeventsモジュールに定義されているものはありません）。

1つの`EventEmitter`コンストラクタの実体で複数のイベントを取り扱うことができます。それぞれのイベントは文字列として表されるイベント名によって識別されます。イベント名は文字列なら何でも構いません（が、分かりやすい名称にするのが望ましいでしょう）。

イベントを発生させるには`emit`関数を使用します。第1引数にイベント名を指定し、第2引数以降にイベントのパラメータを指定します。指定されたイベント名のイベントにイベントハンドラが登録されている場合には、返り値として`true`が返り、登録されていない場合には`false`が返ります。

イベントにイベントハンドラを登録するには`on`関数を使用します。第1引数にイベント名を指定し、第2引数にイベントが発生した際に呼び出される関数（イベントハンドラ）を指定します。この関数は引数としてイベントのパラメータを受け取ります。返り値として`on`関数が呼び出された`EventEmitter`コンストラクタの実体そのものが返ります。

`on`関数の代わりに`addListener`関数を使用することもできます。使い方は全く同じです。

次のコードは`emit`関数と`on`関数の使用例です。

```js
> let events = require("events")
let events = require("events")
undefined
> let ee = new events.EventEmitter()
undefined
> ee.emit("initialized", {name: "pizyumi", age: 25})
false
> ee.on("initialized", function(p) {
... console.log("initialized: " + p.name + " " + p.age);
... })
EventEmitter {
  domain:
   Domain {
     domain: null,
     _events: { error: [Function] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] },
  _events: { initialized: [Function] },
  _eventsCount: 1,
  _maxListeners: undefined }
> ee.emit("initialized", {name: "pizyumi", age: 25})
initialized: pizyumi 25
true
> ee.on("initialized", function(p) {
... console.log("initialized!!");
... })
EventEmitter {
  domain:
   Domain {
     domain: null,
     _events: { error: [Function] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] },
  _events: { initialized: [ [Function], [Function] ] },
  _eventsCount: 1,
  _maxListeners: undefined }
> ee.emit("initialized", {name: "pizyumi", age: 25})
initialized: pizyumi 25
initialized!!
true
> ee.emit("!?&", {name: "pizyumi", age: 25})
false
> ee.on("!?&", function(p) {
... console.log("!?&");
... })
EventEmitter {
  domain:
   Domain {
     domain: null,
     _events: { error: [Function] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] },
  _events: { initialized: [ [Function], [Function] ], '!?&': [Function] },
  _eventsCount: 2,
  _maxListeners: undefined }
> ee.emit("!?&", {name: "pizyumi", age: 25})
!?&
true
```

イベントハンドラが登録されていない状態でイベントを発生させても何も起こりませんが、イベントハンドラを登録すると登録したものは全てイベントが発生した時に呼び出されます。イベントハンドラはイベントに登録された順番に呼び出されます。また、イベント名は文字列なら何でも良いので、`!?&`のようなイベント名でも正常に動作します。

イベントが発生した際に呼び出されるイベントハンドラに渡されるイベントのパラメータは呼び出される全てのイベントハンドラで同じものです。そのため、イベントハンドラの中で引数として渡されたイベントのパラメータの内容を変更すると、その変更はそれ以降に呼び出されるイベントハンドラにも反映されます。

```js
> ee.on("initialized", function(p) {
... p.age = 5;
... console.log("changed");
... })
EventEmitter {
  domain:
   Domain {
     domain: null,
     _events: { error: [Function] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] },
  _events:
   { initialized: [ [Function], [Function], [Function] ],
     '!?&': [Function] },
  _eventsCount: 2,
  _maxListeners: undefined }
> ee.on("initialized", function(p) {
... console.log("initialized: " + p.name + " " + p.age);
... })
EventEmitter {
  domain:
   Domain {
     domain: null,
     _events: { error: [Function] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] },
  _events:
   { initialized: [ [Function], [Function], [Function], [Function] ],
     '!?&': [Function] },
  _eventsCount: 2,
  _maxListeners: undefined }
> ee.emit("initialized", {name: "pizyumi", age: 25})
initialized: pizyumi 25
initialized!!
changed
initialized: pizyumi 5
true
```

イベントから既に登録されているイベントハンドラの登録を解除するには`removeListener`関数又は`removeAllListeners`関数を使用します。

`removeAllListeners`関数を引数なしで呼び出した場合には、`EventEmitter`コンストラクタの実体に登録されている全てのイベントハンドラが登録解除されます。第1引数にイベント名を指定して呼び出した場合には、イベント名のイベントに登録されている全てのイベントハンドラが登録解除されます。返り値として`removeAllListeners`関数が呼び出された`EventEmitter`コンストラクタの実体そのものが返ります。

登録されている特定のイベントハンドラのみを登録解除したい場合には`removeListener`関数を使用します。第1引数にイベント名を指定し、第2引数にイベントハンドラ自体を指定します。返り値として`removeListener`関数が呼び出された`EventEmitter`コンストラクタの実体そのものが返ります。

```js
> ee.removeAllListeners("initialized")
EventEmitter {
  domain:
   Domain {
     domain: null,
     _events: { error: [Function] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] },
  _events: { '!?&': [Function] },
  _eventsCount: 1,
  _maxListeners: undefined }
> ee.emit("!?&", {name: "pizyumi", age: 25})
!?&
true
> ee.emit("initialized", {name: "pizyumi", age: 25})
false
> ee.removeAllListeners()
EventEmitter {
  domain:
   Domain {
     domain: null,
     _events: { error: [Function] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] },
  _events: {},
  _eventsCount: 0,
  _maxListeners: undefined }
> ee.emit("!?&", {name: "pizyumi", age: 25})
false
> ee.emit("initialized", {name: "pizyumi", age: 25})
false
> let handler = function(p) {
... console.log("handler: " + p.name + " " + p.age);
... }
undefined
> ee.on("initialized", handler)
EventEmitter {
  domain:
   Domain {
     domain: null,
     _events: { error: [Function] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] },
  _events: { initialized: [Function] },
  _eventsCount: 1,
  _maxListeners: undefined }
> ee.emit("initialized", {name: "pizyumi", age: 25})
handler: pizyumi 25
true
> ee.removeListener("initialized", handler)
EventEmitter {
  domain:
   Domain {
     domain: null,
     _events: { error: [Function] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] },
  _events: {},
  _eventsCount: 0,
  _maxListeners: undefined }
> ee.emit("initialized", {name: "pizyumi", age: 25})
false
```

全く同じイベントハンドラを同じイベントに複数回登録することもできます。そのような場合、`removeListener`関数でイベントハンドラを登録解除した際には同じイベントハンドラは1回の呼び出しで一気に登録解除されるのではなく、1つずつ最も新しく登録されたものから順に登録解除されます。

```js
> ee.on("initialized", handler)
EventEmitter {
  domain:
   Domain {
     domain: null,
     _events: { error: [Function] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] },
  _events: { initialized: [Function] },
  _eventsCount: 1,
  _maxListeners: undefined }
> ee.on("initialized", function() {
... console.log("event");
... })
EventEmitter {
  domain:
   Domain {
     domain: null,
     _events: { error: [Function] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] },
  _events: { initialized: [ [Function], [Function] ] },
  _eventsCount: 1,
  _maxListeners: undefined }
> ee.on("initialized", handler)
EventEmitter {
  domain:
   Domain {
     domain: null,
     _events: { error: [Function] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] },
  _events: { initialized: [ [Function], [Function], [Function] ] },
  _eventsCount: 1,
  _maxListeners: undefined }
> ee.emit("initialized", {name: "pizyumi", age: 25})
handler: pizyumi 25
event
handler: pizyumi 25
true
> ee.removeListener("initialized", handler)
EventEmitter {
  domain:
   Domain {
     domain: null,
     _events: { error: [Function] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] },
  _events: { initialized: [ [Function], [Function] ] },
  _eventsCount: 1,
  _maxListeners: undefined }
> ee.emit("initialized", {name: "pizyumi", age: 25})
handler: pizyumi 25
event
true
```

登録されてから初めてのイベントの発生の際にのみ呼び出されるイベントハンドラを登録したい場合には（登録されてから初めてのイベントの発生の後に自動的に登録解除されるイベントハンドラを登録したい場合には）、`on`関数の代わりに`once`関数を使用します。使い方は全く同じです。

```js
> ee.once("initialized", function() {
... console.log("once");
... })
EventEmitter {
  domain:
   Domain {
     domain: null,
     _events: { error: [Function] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] },
  _events: { initialized: [ [Function], [Function], [Object] ] },
  _eventsCount: 1,
  _maxListeners: undefined }
> ee.emit("initialized", {name: "pizyumi", age: 25})
handler: pizyumi 25
event
once
true
> ee.emit("initialized", {name: "pizyumi", age: 25})
handler: pizyumi 25
event
true
```

イベントに登録されている全てのイベントハンドラを取得するには`listeners`関数を使用します。第1引数にイベント名を指定します。

イベントに登録されているイベントハンドラの数を取得するには`listenersCount`関数を使用します。第1引数にイベント名を指定します。

```js
> ee.listeners("initialized")
[ [Function], [Function] ]
> ee.listeners("!?&")
[]
> ee.listenerCount("initialized")
2
> ee.listenerCount("!?&")
0
```

イベントにイベントハンドラを登録する前には`newListener`イベントが発生し、イベントからイベントハンドラを登録解除した後には`removeListener`イベントが発生します。`newListener`イベントと`removeListener`イベントはパラメータとしてイベント名とイベントハンドラを受け取ります。

```js
> ee.on("removeListener", function(event, listener) {
... console.log(event, "listener removed");
... })
EventEmitter {
  domain:
   Domain {
     domain: null,
     _events: { error: [Function] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] },
  _events:
   { initialized: [ [Function], [Function] ],
     removeListener: [Function] },
  _eventsCount: 2,
  _maxListeners: undefined }
> ee.on("newListener", function(event, listener) {
... console.log(event, "listener added");
... })
EventEmitter {
  domain:
   Domain {
     domain: null,
     _events: { error: [Function] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] },
  _events:
   { initialized: [ [Function], [Function] ],
     removeListener: [Function],
     newListener: [Function] },
  _eventsCount: 3,
  _maxListeners: undefined }
> ee.on("initialized", handler)
initialized listener added
EventEmitter {
  domain:
   Domain {
     domain: null,
     _events: { error: [Function] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] },
  _events:
   { initialized: [ [Function], [Function], [Function] ],
     removeListener: [Function],
     newListener: [Function] },
  _eventsCount: 3,
  _maxListeners: undefined }
> ee.removeListener("initialized", handler)
initialized listener removed
EventEmitter {
  domain:
   Domain {
     domain: null,
     _events: { error: [Function] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] },
  _events:
   { initialized: [ [Function], [Function] ],
     removeListener: [Function],
     newListener: [Function] },
  _eventsCount: 3,
  _maxListeners: undefined }
```

1つのイベントに設定されている数より多くのイベントハンドラが登録された場合には警告が表示されます。

```js
> let handler2 = function() {
... console.log("handled");
... }
undefined
> for (let i = 0; i < 20; i ++) {
... ee.on("event", handler2);
... }
event listener added
event listener added
event listener added
event listener added
event listener added
event listener added
event listener added
event listener added
event listener added
event listener added
event listener added
(node) warning: possible EventEmitter memory leak detected. 11 event listeners added. Use emitter.setMaxListeners() to increase limit.
Trace
    at EventEmitter.addListener (events.js:239:17)
    at repl:2:4
    at REPLServer.defaultEval (repl.js:248:27)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)
    at REPLServer.<anonymous> (repl.js:412:12)
    at emitOne (events.js:82:20)
    at REPLServer.emit (events.js:169:7)
    at REPLServer.Interface._onLine (readline.js:210:10)
    at REPLServer.Interface._line (readline.js:549:8)
event listener added
event listener added
event listener added
event listener added
event listener added
event listener added
event listener added
event listener added
event listener added
EventEmitter {
  domain:
   Domain {
     domain: null,
     _events: { error: [Function] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] },
  _events:
   { initialized: [ [Function], [Function] ],
     removeListener: [Function],
     newListener: [Function],
     event:
      [ [Function],
        [Function],
        [Function],
        [Function],
        [Function],
        [Function],
        [Function],
        [Function],
        [Function],
        [Function],
        [Function],
        [Function],
        [Function],
        [Function],
        [Function],
        [Function],
        [Function],
        [Function],
        [Function],
        [Function],
        warned: true ] },
  _eventsCount: 4,
  _maxListeners: undefined }
```

警告が表示されても、イベントハンドラの登録自体は正常に行われています。

```js
> ee.emit("event")
handled
handled
handled
handled
handled
handled
handled
handled
handled
handled
handled
handled
handled
handled
handled
handled
handled
handled
handled
handled
true
```

警告が表示されるイベントハンドラの数を変更するには、`EventEmitter.defaultMaxListeners`か`setMaxListeners`関数を使用します。`EventEmitter.defaultMaxListeners`は全ての`EventEmitter`コンストラクタの実体で適用される設定を表す変数です。`setMaxListeners`関数はその`EventEmitter`コンストラクタの実体で適用される設定を設定するための関数です。第1引数に設定値を指定します。`EventEmitter.defaultMaxListeners`の設定より`setMaxListeners`関数による設定の方が優先されます。設定値として`Infinity`又は`0`を指定した場合には、どれだけ同じイベントにイベントハンドラを登録しても警告は表示されなくなります。また、その`EventEmitter`コンストラクタの実体で適用されている設定を取得するには`getMaxListeners`関数を使用します。

特別なイベントとして`error`イベントがあります。`error`イベントのイベントハンドラが1つもない状態で`error`イベントが発生した場合、スタックトレースが出力され、プログラムが終了します。

```js
> ee.emit("error", new Error("something error"))
Error: something error
    at repl:1:18
    at REPLServer.defaultEval (repl.js:248:27)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)
    at REPLServer.<anonymous> (repl.js:412:12)
    at emitOne (events.js:82:20)
    at REPLServer.emit (events.js:169:7)
    at REPLServer.Interface._onLine (readline.js:210:10)
    at REPLServer.Interface._line (readline.js:549:8)
    at REPLServer.Interface._ttyWrite (readline.js:826:14)
> false
```

`error`イベントに登録されているイベントハンドラが存在する場合、このような特別な動作が実行されることはありません。