Node.jsのEventEmitterについていろいろ

Node.jsのEventEmitterについて書いていきます。

## EventEmitterとは
Node.jsにおいて独自のイベントを作成したいときに使われます。

EventEmitterの基本的な使い方は、`on`や`once`メソッドなどを使って、イベント名を登録して、リスナーに登録したイベントが呼び出されたときに実行する処理を書いていきます。ここでいうイベントの呼び出しとは、`emit`メソッドでイベント名を指定して登録したイベントを呼び出します。

`'start'`イベントと`'end'`イベントを実装した簡単な例をやってみたいと思います：

```js
const { EventEmitter } = require("events");
const eventEmitter = new EventEmitter();

eventEmitter.on('start', () => {
  console.log('start event!');
});

// This method don't run.
eventEmitter.on('end', () => {
  console.log('end event!');
});

eventEmitter.emit('start');
```

これを実行すると`'start'`イベントのみをemitしているので、`'start'`イベントは実行され、`'end'`イベントは実行されません。

これを図にすると以下のようになると思います。
![eventemitter-qiita1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144079/ad43237c-f7c2-cb6a-896c-f5169aabad4f.jpeg)

`on`メソッドは第一引数にイベント名、第二引数にはリスナーをコールバック関数で実装します。

`emit`メソッドは指定したイベント名が登録されていれば、イベントを呼び出して、リスナーを実行します。第一引数にはイベント名が入ります。その他にも引数が存在しますがそれはこの後に説明したいと思います。

## リスナーに引数を渡す
EventEmitterでリスナーに対して引数を渡したいときは、まずイベントを登録するメソッドのリスナーの実装部分で引数を設定します。そのあとに、`emit`メソッドで第二引数以降に登録したイベントに対応した引数を入れていくとリスナーに引数を渡すことができます。

実際に実装してみるとこんな感じです：

```js
eventEmitter.on('setting', (obj, version) => {
  console.log('name: ' + obj.name + ' message: ' + obj.message);
  console.log('This version is ' + version);
});

eventEmitter.emit('setting', {name: 'event setting', message: 'Hello!!'}, '1.3.4');

// Print
// name: event setting message: Hello!!
// This version is 1.3.4
```

## そのほかのメソッド
ここでは`on`と`emit`メソッド以外で気になったものを書いていきたいと思います。

### onceメソッド
最初の説明で`once`メソッドというのがありましたが、このメソッドは基本的には`on`メソッドと変わりませんが、このメソッドで登録したイベントは**一度しか**呼び出されないということです。

実際にさっきのコードの`on`メソッドのどれかを`once`メソッドに書き換えた後に、同じイベントを`emit`メソッドで複数回呼び出してみてください。`once`メソッドで登録したイベントは一度しか実行されていないと思います、

```js
eventEmitter.once('setting', (obj, version) => {
  console.log('name: ' + obj.name + ' message: ' + obj.message);
  console.log('This version is ' + version);
});

eventEmitter.emit('setting', {name: 'event setting', message: 'Hello!!'}, '1.3.4');
eventEmitter.emit('setting', {name: 'event setting', message: 'Hello!!'}, '1.3.4');
```

### リスナーの追加と削除
リスナーの追加と削除は`addListener`と`removeListener`メソッドでできます。

リスナーに追加したいときは、`addListener`メソッドの第一引数にリスナーを追加したいイベント名、第二引数に実際に追加するリスナーを実装します。

リスナーを削除したいときは、`removeListener`メソッドの第一引数に削除したいリスナーのあるイベント名、第二引数には削除したいリスナーを入れます。注意してほしいのがこの第二引数には無名関数ではなく、リスナーの関数名を入れること。

例えば、以下の図のように`'box'`イベントにリスナーを追加する`'addItem1'`と`'addItem2'`イベントを作成して、`'addItem1'`イベントで追加したリスナーを`'removeItem'`イベントで削除するイベントを作成します。
![eventemitter-qiita2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144079/c56f67cd-03c3-ac8c-790a-95f27f8030fd.jpeg)

コードにするとこんな感じです：

```js
const { EventEmitter } = require("events");
const eventEmitter = new EventEmitter();

eventEmitter.on('box', () => {
  console.log('listener1');
  console.log('Sum of this listener is ' + eventEmitter.listenerCount('box'));
});;

const listenerMessage1 = () => console.log('This is listener1');
const listenerMessage2 = () => console.log('This is listener2');

eventEmitter.on('addItem1', () => {
  console.log('add listener');
  eventEmitter.addListener('box', listenerMessage1);
});

eventEmitter.on('addItem2', () => {
  console.log('add listener');
  eventEmitter.addListener('box', listenerMessage2);
});

eventEmitter.on('removeItem', () => {
  console.log('remove listener');
  eventEmitter.removeListener('box', listenerMessage1);
});

eventEmitter.emit('addItem1');
eventEmitter.emit('addItem2');
eventEmitter.emit('removeItem');
eventEmitter.emit('box');
```

ついでに、1つのイベントに登録できるリスナーの数はデフォルトでは10個です。この登録できるリスナーの数を変更するには`setMaxListeners`で設定できます。

リスナーを削除するメソッドは他にもあり、すべてのリスナーを削除する（イベント名を指定するとそのイベント内のリスナーすべてを削除する）メソッドである`removeAllListener`や`removeListener`メソッドのエイアリスである`off`メソッドが使えます。

まえにあった`once`メソッドではイベントが呼び出されたあとにこのメソッドで登録したリスナーは削除されます。

### 呼び出す前に呼び出す
特定のイベントの前にリスナーを呼び出したいときに`prependListener`もしくは`prependOnceListener`メソッドで登録できます。二つのメソッドの違いはonceが付いている方が一度だけ呼ばれるということです。

### 特別なイベント
EventEmitterには特別なイベントが存在します。リスナーが登録されたときに呼び出される`newListener`、リスナーが削除されたときに呼び出される`removeListener`があります。

```js
const { EventEmitter } = require("events");
const eventEmitter = new EventEmitter();

var count = 0;

eventEmitter.on('newListener', () => {
  ++count;
  console.log('Number of Listener is ' + count);
});

eventEmitter.on('removeListener', () => {
  --count;
  console.log('RemoveListener!');
  console.log('Number of Listener after remeved is ' + count);
});

eventEmitter.on('start', () => {
  console.log('Start event!');
});

const event1Callback = () => console.log('Add to event1');

eventEmitter.on('e1', event1Callback);

// This method don't run.
eventEmitter.on('end', () => {
  console.log('End event!');
});

eventEmitter.emit('start');
eventEmitter.removeListener('e1', event1Callback);
```

実行すると

```
Number of Listener is 1
Number of Listener is 2
Number of Listener is 3
Number of Listener is 4
Start event!
RemoveListener!
```

となります。

実際に、`on`メソッドでリスナーが追加されるたびに、`newListener`イベントが実行されています。そして、リスナーが削除されたときには`removeListener`イベントが実行されていることがわかります。

## さいご
実験的なものですが、`captureRejections`について調べたかったのですがあまり情報が得られなかったので、いったん保留にしてます。機会があったらここに追記しておきます。

あと、いろいろ調べてみて、Node.jsのEventEmitterがなかなかに不評で、EventEmitter2や3とかがあったりしてて面白かったです。自分が最近ハマっているPuppeteerではバージョンが4.0.0になって、EventEmitterをなくしてMittというライブラリにしていこうという動きがあったりとか...