hakobera's blog

技術メモ。たまに雑談

EventEmitter.emit() によくある勘違い

突然ですが、Node.jsで次のプログラムを実行した結果を答えてください。

var EventEmitter = require('events').EventEmitter;
var event = new EventEmitter();

console.log('1');
event.on('open', function () {
  console.log('2');
});
event.emit('open');
console.log(3);

正解は少し下の方に書いてあります。 少しだけスクロールを我慢して考えてみてください。









正解は

1
2
3

です。

1
3
2

だと思っていた人も多いのではないでしょうか。(私だけかもしれませんが)

つまり、表題の「EventEmitter.emit() によくある勘違い」とは、EventEmitter.emit()が次のイベントループで実行されるノンブロッキング処理である、という点です。しかし、EventEmitter.emit()はコールバックを同期呼出しする、つまり、ブロッキングします。

では、1, 3, 2 と出力したい場合は、どう書くかというと、次のように書きます。

var EventEmitter = require('events').EventEmitter;
var event = new EventEmitter();

console.log('1');
event.on('open', function () {
  process.nextTick(function () {
    console.log('2');
  });
});
event.emit('open');
console.log(3);

もしくはこう。

var EventEmitter = require('events').EventEmitter;
var event = new EventEmitter();

console.log('1');
event.on('open', function () {
  console.log('2');
});
process.nextTick(function () {
  event.emit('open');
});
console.log(3);

開発者はコールバック関数の中でprocess.nextTick()を呼び出すか、emit()自体をprocess.nextTick()のコールバック内で呼び出すことで、処理を次のイベントループに回すことができます。 emit()がコールバックを同期呼び出しするのは、開発者に処理をブロッキングで処理するのか、ノンブロッキングで処理するのかの選択肢を与えるために、意図してそのようにデザインされているのです。

考えてみれば、当たり前のことなのですが、自分がついこの間まで勘違いしていたので、戒めも兼ねて書いてみました。 みなさんも思い込みには気をつけて、必ず動作を確認するようにしましょう。