Node.js EventEmitter
Events
Node.jsコアAPIの多くは特定の種類のオブジェクト(エミッターと呼ばれる)が呼ばれるためにFunctionオブジェクト(リスナー)を起こす名付けられたイベントを出す慣用的な非同期イベントドリブンアーキテクチャを作る。
イベントを出すすべてのオブジェクトはEventEmitterクラスのインスタンスである。これらのオブジェクトはオブジェクトによって出した名付けられたイベントを付けるために1つ以上の関数を与えるeventEmitter.on()を出す。イベント名はキャメルケース文字列だが、有効なJavaScriptプロパティキーは使うことできる。
EventEmitterオブジェクトはイベントを出すとき、すべての関数は特定のイベントが同期を呼ぶためにつける、呼び出されたリスナーから返された値はすべて無視され、破棄される。
以下の例は単一リスナーによって単純なEventEmitterインスタンスをみせる。eventEmitter.emit()メソッドはイベントをトリガーするために使う間、eventEmitter.on()メソッドはリスナーを登録するために使う。
code:js
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
console.log('an event occurred!');
});
myEmitter.emit('event');
リスナーの引数とthisを渡す
eventEmitter.emit()メソッドはリスナー関数を渡すために任意の引数の集合を与える。普通のリスナー関数が呼ばれたときに、標準のthisキーワードはリスナーがつけられたEventEmitterインスタンスを参照するための集合。
code:js
const myEmitter = new MyEmitter();
myEmitter.on('event', function(a, b) {
console.log(a, b, this, this === myEmitter);
// Prints:
// a b MyEmitter {
// domain: null,
// _eventsCount: 1,
// _maxListeners: undefined } true
});
myEmitter.emit('event', 'a', 'b');
リスナーとしてES6アロー関数を使うことができるが、thisキーワードはEventEmitterインスタンスを参照しない:
code:js
const myEmitter = new MyEmitter();
myEmitter.on('event', (a, b) => {
console.log(a, b, this);
// Prints: a b {}
});
myEmitter.emit('event', 'a', 'b');
非同期 vs 同期
EventEmitterは登録された要求に同期的にすべてのリスナーを呼ぶ。これはイベントの適切な順序付けを確保し、競合状態と論理エラーを回避するのに役立つ。リスナー関数はsetImmediate()もしくはprocess.nextTick()メソッドを使ってオペレーションンの非同期モードに切り替えることができる:
code:js
const myEmitter = new MyEmitter();
myEmitter.on('event', (a, b) => {
setImmediate(() => {
console.log('this happens asynchronously');
});
});
myEmitter.emit('event', 'a', 'b');
イベントを1回だけ処理する
リスナーがeventEmitter.on()メソッドを使って登録されるとき、そのリスナーは名付けられたイベントが出るごとに呼び出される。
code:js
const myEmitter = new MyEmitter();
let m = 0;
myEmitter.on('event', () => {
console.log(++m);
});
myEmitter.emit('event');
// Prints: 1
myEmitter.emit('event');
// Prints: 2
eventEmitter.once()メソッドを使うことで、一部のイベントの一度でも呼ばれたリスナーを登録することができる。一度のイベントが出されて、リスナーは登録されずに、そのときに呼ばれる。
code:js
const myEmitter = new MyEmitter();
let m = 0;
myEmitter.once('event', () => {
console.log(++m);
});
myEmitter.emit('event');
// Prints: 1
myEmitter.emit('event');
// Ignored
エラーイベント
エラーがEventEmitterインスタンス内で起きるとき、典型的なアクションを出すためのerrorイベントである。Node.js内で特別な要素として扱われる。
もしEventEmitterがerrorイベントを登録した1つのリスナーを持たない場合、errorイベントは出され、エラーが投げられ、スタックトレースがプリントされ、Node.jsプロセスが終了する。
code:js
const myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));
// Throws and crashes Node.js
Node.jsプロセスをキャッシュすることから守るためにdomainモジュールは使うことができる(domainモジュールは非推奨)。
ベストプラクティスとして、リスナーは常にerrorイベントを追加するべき。
code:js
const myEmitter = new MyEmitter();
myEmitter.on('error', (err) => {
console.error('whoops! there was an error');
});
myEmitter.emit('error', new Error('whoops!'));
// Prints: whoops! there was an error
シンボルerrorMonitorを使ってリスナーをインストールするために出されたエラーを消費しないでerrorイベントを監視することができる。
code:js
const myEmitter = new MyEmitter();
myEmitter.on(EventEmitter.errorMonitor, (err) => {
MyMonitoringTool.log(err);
});
myEmitter.emit('error', new Error('whoops!'));
// Still throws and crashes Node.js
Promiseのリジェクションをキャプチャする
例外が投げられた場合、処理されないリジェクションを招く可能性があるので、イベントハンドラーによってasync関数を使うことで問題がでる:
code:js
const ee = new EventEmitter();
ee.on('something', async (value) => {
throw new Error('kaboom');
});
Promise上のハンドラー(.then(undefined, handler))をインストールするために、EventEmitterコンストラクタのcaptureRejectionsオプションもしくはグローバルな設定はこのふるまいを変更する。このハンドラーは、非同期で例外をSymbol.for( 'nodejs.rejection')メソッドにルーティングする。存在しない場合はerroイベントハンドラーにルーティングする。
code:js
const ee1 = new EventEmitter({ captureRejections: true });
ee1.on('something', async (value) => {
throw new Error('kaboom');
});
ee1.on('error', console.log);
const ee2 = new EventEmitter({ captureRejections: true });
ee2.on('something', async (value) => {
throw new Error('kaboom');
});
EventEmitter.captureRejections = trueを設定することはEventEmitterのすべての新しいインスタンスのデフォルトを変更する。
code:js
EventEmitter.captureRejections = true;
const ee1 = new EventEmitter();
ee1.on('something', async (value) => {
throw new Error('kaboom');
});
ee1.on('error', console.log);
captureRejectionsのふるまいによって生成されるerrorイベントはエラーの無限ループを避けるためにcatchハンドラーを持たない:推奨はerrorイベントハンドラーとしてasync関数を使わないべき。
Class: EventEmitter
EventEmitterクラスはeventsモジュールによって定義され出される:
code:js
const EventEmitter = require('events');
すべてのEventEmitterは新しいリスナーが加えられたときにnewListenerを出し、存在しているリスナーが削除されたときremoveListenerを出す。
以下のオプションをサポートする:
captureRejections boolean promiseリジェクションを自動的にキャプチャすることができる。
イベント: newListener
eventName string | symbol リッスンされたイベントの名前
listenerFunction イベントハンドラ関数
リスナーがリスナーの配列の内部び追加する前に、EventEmitterインスタンスはnewListenerイベントを出す。
newListenerイベントを登録したリスナーはイベント名を渡して、加えられたリスナーを参照する。
イベントがリスナーを追加する前にトリガーされる事実は微妙だが、重要な副作用である:追加のリスナーは追加しているプロセスであるリスナーの前に挿入されるnewListenerコールバック内で同じnameを登録する。
code:js
const myEmitter = new MyEmitter();
// Only do this once so we don't loop forever
myEmitter.once('newListener', (event, listener) => {
if (event === 'event') {
// Insert a new listener in front
myEmitter.on('event', () => {
console.log('B');
});
}
});
myEmitter.on('event', () => {
console.log('A');
});
myEmitter.emit('event');
// Prints:
// B
// A
イベント: removeListener
eventName string | symbol イベント名
listener Function イベントハンドラ関数
removeListenerイベントはlistenerが削除されたあとに出される。
EventEmitter.defaultMaxListeners
デフォルトで、最大10個のリスナーは単一イベントを登録することができる。この制限はemitter.setMaxListeners(n)メソッドを使うことで見えないEventEmitterリスナーを変更することができる。すべてEventEmitterインスタンスのデフォルトを変更するために、EventEmitter.defaultMaxListenersプロパティは使うことができる。もしこの値が整数でない場合、TypeErrorがスローされる。
変更が作られる前に作ったことを含むために、変更がすべてのEventEmitterインスタンスに影響するので、EventEmitter.defaultMaxListenerを設定するときに注意すること。しかし、emitter.setMaxListeners(n)を呼ぶことはemitter.setMaxListenersを介して優先する。
これは厳しい制限ではない。EventEmitterインスタンスは加えられたより多くのリスナーを与えるが、検出された”EventEmitterのメモリリークの可能性”を示すことでstderrを警告する痕跡を出力する。単一EventEmitterのため、emitter.getMaxListeners()とemitter.setMaxListeners()メソッドはこの警告を一時的に避けるために使われる:
code:js
emitter.setMaxListeners(emitter.getMaxListeners() + 1);
emitter.once('event', () => {
// do stuff
emitter.setMaxListeners(Math.max(emitter.getMaxListeners() - 1, 0));
});
--trace-warningsコマンドラインフラグは警告のようなスタックトレースを表示されるために使うことができる。
出された警告はprocess.on('warning')で検査することができ、イベントエミッターインスタンス、イベントの名前、付けられたリスナーの数に参照するために、追加のemitter、type、countプロパティを持つ。そのnameプロパティはMaxListenersExceededWarningをセットする。
EventEmitter.errorMonitor
このシンボルはerrorイベントを監視しているリスナーをインストールするために使われる。リスナーは標準errorリスナーが呼ばれる前に呼ばれたこのシンボルを使うことでインストールした。
このシンボルを使うリスナーをインストールすることはerrorイベントが出された振る舞いを変更せず、もし標準でないerrorリスナーがインストールされた場合、プロセスはクラッシュする。
emitter.addListener(eventName, listener)
eventName string | symbol
listener Function
emitter.on(eventName, listener)のエイアリス。
emitter.emit(eventName/, ...args/)
eventName string | symbol
...arg any
return: boolean
eventNameという名前のイベントに登録されたリスナーを、登録された順に同期的に呼び出します。
もしイベントがリスナーを持つ場合、trueを返す。
code:js
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
// First listener
myEmitter.on('event', function firstListener() {
console.log('Helloooo! first listener');
});
// Second listener
myEmitter.on('event', function secondListener(arg1, arg2) {
console.log(event with parameters ${arg1}, ${arg2} in second listener);
});
// Third listener
myEmitter.on('event', function thirdListener(...args) {
const parameters = args.join(', ');
console.log(event with parameters ${parameters} in third listener);
});
console.log(myEmitter.listeners('event'));
myEmitter.emit('event', 1, 2, 3, 4, 5);
// Prints:
// [
// ]
// Helloooo! first listener
// event with parameters 1, 2 in second listener
// event with parameters 1, 2, 3, 4, 5 in third listener
emitter.eventNames()
エミッターはリスナーを登録したイベントをリストしている配列を返す。配列の値は文字列もしくはSymbol。
code:js
const EventEmitter = require('events');
const myEE = new EventEmitter();
myEE.on('foo', () => {});
myEE.on('bar', () => {});
const sym = Symbol('symbol');
myEE.on(sym, () => {});
console.log(myEE.eventNames());
emitter.getMaxListeners()
emitter.setMaxListeners(s)によってセットするかEventEmitter.defaultMaxListenersのデフォルトにするかのどちらかであるEventEmitterの現在の最大リスナー値を返す。
emitter.listenerCount(eventName)
eventName string | symbol リッスンされたイベントの名前
eventNameを名付けたイベントにリッスンしているリスナーの数を返す。
emitter.listeners(eventName)
eventName string | symbol
eventNameを名付けたイベントをリッスンしているリスナーの配列のコピーを返す。
code:js
server.on('connection', (stream) => {
console.log('someone connected!');
});
console.log(util.inspect(server.listeners('connection')));
emitter.off(eventName, listener)
eventName string | symbol
listener Function
emitter.removeListener()のエイアリス。
emitter.on(eventName, listener)
eventName string | symbol イベントの名前
listener Function コールバック関数
eventNameという名前のイベントのリスナー配列の最後にリスナー関数を追加する。もしlistenerが既に追加されていた場合、確認のためのチェックは行われません。eventNameとlistenerの同じ組み合わせを渡す複数呼び出しは複数回追加され、呼ばれたlistenerの結果を出す。
code:js
server.on('connection', (stream) => {
console.log('someone connected!');
});
呼び出しはつなぐことができるので、呼び出しはEventEmitterの参照を返す、
デフォルトで、イベントリスナー追加された順序で呼び出される。emitter.prependListener()メソッドはリスナー配列の開始のイベントリスナーを追加するための代わりとして使うことができる。
code:js
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a
emitter.once(eventName, listener)
eventName string | symbol イベントの名前
listener Function コールバック関数
eventNameという名前のイベントの一回限りのlistener関数を追加する。次のeventNameはトリガーされ、子のリスナーは削除され、その時呼び出される。
code:js
server.once('connection', (stream) => {
console.log('Ah, we have our first user!');
});
呼び出しはつなぐことができるので、呼び出しはEventEmitterの参照を返す、
デフォルトで、イベントリスナー追加された順序で呼び出される。emitter.prependOnceListener()メソッドはリスナー配列の開始のイベントリスナーを追加するための代わりとして使うことができる。
code:js
const myEE = new EventEmitter();
myEE.once('foo', () => console.log('a'));
myEE.prependOnceListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a
emitter.prependListener(eventName, listener)
eventNameという名前のイベントのリスナー配列の初めのlistener関数を加える。もしlistenerが既に追加された場合、確認なしで見ることができる。eventNameとlistenerの同じ組み合わせを渡す複数呼び出しは複数回追加され、呼ばれたlistenerの結果を出す。
code:js
server.prependListener('connection', (stream) => {
console.log('someone connected!');
});
emitter.prependOnceListener(eventName, listener)
リスナー配列の初めにeventNameという名前のイベントの一回限りのlistener関数を追加する。次のeventNameはトリガーされ、子のリスナーは削除され、その時呼び出される。
code:js
server.prependOnceListener('connection', (stream) => {
console.log('Ah, we have our first user!');
});
emitter.removeAllListeners(/eventName/)
すべてのリスナーもしくは指定されたeventNameを削除する。
EventEmitterインスタンスが他のいくつかのコンポーネントもしくはモジュール(すなわち、socketもしくはstream)によって作られるとき、コードの他の場所に追加したリスナーを削除するための悪いプラクティス。
呼び出しはつなぐことができるので、EventEmitterの参照を返す。
emitter.removeListener(eventName, listener)
eventNameという名前のイベントのリスナー配列から指定したlistenerを削除する。
code:js
const callback = (stream) => {
console.log('someone connected!');
};
server.on('connection', callback);
// ...
server.removeListener('connection', callback);
removeListener()はリスナー配列からリスナーの1つのインスタンスを削除する。もし単一のリスナーが指定したeventNameのリスナー配列に複数回追加されたなら、removeListener()はさまざまなインスタンスを削除するために複数回呼び出される。
一度イベントは出され、すべてのリスナーは順番に呼び出されるエミッタリングを付ける。これはエミッタリングした後と進行時にemit()から削除しない実行を終えるさいごのリスナーの前にremoveListener()もしくはremoveAllListeners()呼び出しを実装する。サブシーケンスイベントは期待される振るまいである。
code:js
const myEmitter = new MyEmitter();
const callbackA = () => {
console.log('A');
myEmitter.removeListener('event', callbackB);
};
const callbackB = () => {
console.log('B');
};
myEmitter.on('event', callbackA);
myEmitter.on('event', callbackB);
// callbackA removes listener callbackB but it will still be called.
myEmitter.emit('event');
// Prints:
// A
// B
// callbackB is now removed.
myEmitter.emit('event');
// Prints:
// A
なぜなら、削除され始めるリスナーの後に登録されたリスナーの位置インデックスを変更するリスナーを呼び出すために、リスナーは内部の配列を使うことで管理される。これはリスナーが呼ばれる順番で実装するが、再作成される必要があるemitter.listeners()メソッドによって返したリスナー配列のコピーであるという意味。
単一の関数が単一のイベントで複数回ハンドラーとして追加されるとき、removeListener()はもっとも最近追加されたインスタンスを削除する。例で、once('ping')リスナーは削除される:
code:js
const ee = new EventEmitter();
function pong() {
console.log('pong');
}
ee.on('ping', pong);
ee.once('ping', pong);
ee.removeListener('ping', pong);
ee.emit('ping');
ee.emit('ping');
呼び出しはつなぐことができるので、EventEmitterの参照を返す。
emitter.setMaxListeners(n)
デフォルトで、もし10以上のリスナーが一部のイベントを加えるならEventEmitterは警告を出す。これはメモリリークの発見しやすくするためにデフォルトで使用できる。emitter.setMaxListeners()メソッドはこの指定したEventEmitterインスタンスを編集されるための制限まで許可する。値はリスナーの無制限の数を示すためにInfinity(もしくは0)をセットすることができる。
呼び出しはつなぐことができるので、EventEmitterの参照を返す。
emitter.rawListeners(eventName)
(once()によって作られたような)ラッパーを含むため、eventNameという名前のイベントのリスナーの配列のコピーを返す。
code:js
const emitter = new EventEmitter();
emitter.once('log', () => console.log('log once'));
// Returns a new Array with a function onceWrapper which has a property
// listener which contains the original listener bound above
const listeners = emitter.rawListeners('log');
const logFnWrapper = listeners0; // Logs "log once" to the console and does not unbind the once event
logFnWrapper.listener();
// Logs "log once" to the console and removes the listener
logFnWrapper();
emitter.on('log', () => console.log('log persistently'));
// Will return a new Array with a single function bound by .on() above
const newListeners = emitter.rawListeners('log');
// Logs "log persistently" twice
emitter.emit('log');
emitter/Symbol.for('nodejs.rejection')/(err, eventName/, ...args/)
captureRejectionsは実験的なもの。
Symbol.for('nodejs.rejection')メソッドはイベントを出すこととcaptureRejectionsがエミッター上で有効であるとき、promise rejectionが起こる時に呼び出される。Symbol.for('nodejs.rejection')の場所でevents.captureRejectionSymbolを使うことができる。
code:js
const { EventEmitter, captureRejectionSymbol } = require('events');
class MyClass extends EventEmitter {
constructor() {
super({ captureRejections: true });
}
console.log('rejection happened for', event, 'with', err, ...args);
this.destroy(err);
}
destroy(err) {
// Tear the resource down here.
}
}
events.once(emitter, name)
EventEmitterが与えられたイベントを出す時にいっぱいにされ、EventEmitterが'error'を出す時に拒絶されたPromiseを作る。Promiseは与えられたイベントを出したすべての引数の配列によって解決する。
このメソッドはあえて汎用的であり、webプラットフォームEventTargetインターフェースによって動作する。'error'イベントをリッスンしない。
code:js
const { once, EventEmitter } = require('events');
async function run() {
const ee = new EventEmitter();
process.nextTick(() => {
ee.emit('myevent', 42);
});
const value = await once(ee, 'myevent'); console.log(value);
const err = new Error('kaboom');
process.nextTick(() => {
ee.emit('error', err);
});
try {
await once(ee, 'myevent');
} catch (err) {
console.log('error happened', err);
}
}
run();
events.captureRejections
captureRejectionsは実験的なもの。
全ての新しいEventEmitterオブジェクト上でデフォルトのcaptureRejectionsを変更する。
events.captureRejectionSymbol
captureRejectionsは実験的なもの。
events.on(emitter, eventName)
code:js
const { on, EventEmitter } = require('events');
(async () => {
const ee = new EventEmitter();
// Emit later on
process.nextTick(() => {
ee.emit('foo', 'bar');
ee.emit('foo', 42);
});
for await (const event of on(ee, 'foo')) {
// The execution of this inner block is synchronous and it
// processes one event at a time (even with await). Do not use
// if concurrent execution is required.
console.log(event); // prints 'bar' 42 }
// Unreachable here
})();
eventNameイベントを繰り返すAsyncIteratorを返す。もしEventEmitterが'error'を出すならスローする。ループをやめるときに全てのイテレータを削除する。valueは出されたイベント引数を構成した配列である様々な反復によって返す。