EventEmitterはいつ使うの?
非同期のJavaScriptにはCallbackパタンがよく使われてる。Node.js開発経験のある方ならわかると思うが、処理が長くなると、コードが階段状になりがちなのだ:
callme(function(done){
callme(function(){
callme(function(){
callme(function(){
done();
});
});
});
});
Callbackの処理が簡単な場合は、この階段状のコードでも特に問題ないだが、もし複雑の処理になると、かなり読みづらい。そこで、EventEmitter
を使って処理結果をEventとして外に出すのが解決方の一つ。
EventEmitterの使い方
EventEmitter
はnode.jsevents
モジュールの中に入ってる。EventEmitter
を継承することで、on
とemit
メソードでEventの受け取りと転送ができる。サンプルを作成してみよう:
var util = require('util');
var events = require('events');
var fs = require('fs');
var async = require('async');
// Event名が正しさを確保するために、変数を使う
var _e = {
data : 'data',
end : 'end',
error: 'error',
};
// DirReaderクラス
function DirReader(dir){
events.EventEmitter.call(this);
// dirをセット
this.dir = dir;
}
util.inherits(DirReader, events.EventEmitter);
DirReader.prototype.read = function() {
var self = this;
var count = 0;
// ディレクトリーに入ってるファイルを読み出す
fs.readdir(this.dir, function(err, files) {
// エラーを直接throwするより、「error」イベントとして外にだす
if (err) return self.emit(_e.error, err);
// すべてのファイル中身を順番に読み出す
async.eachSeries(files, function(file, done) {
fs.readFile(file, function(err, data) {
if (err) return done(err);
// 無事に読み出せば、データを「data」イベントとして外にだす
self.emit(_e.data, data);
count++;
done();
});
}, function(err) {
if (err) return self.emit(_e.error, err);
// 処理終了を「end」イベントとして出す。
self.emit(_e.end, count);
});
});
};
このDirReader
モジュールは下記のように使える:
var reader = DirReader('./path');
reader.on('data', function(data){
console.log(data);
});
reader.on('end', function(count){
console.log('ファイル' + count + '個を処理しました。');
});
reader.on('error', function(err){
console.log(err);
});
処理結果をイベント形式で外に出すと、複雑な処理をやってもコードの可読性を保てる。実はnode.jsのapiにもこのパターンが大量に使われている。例えば、streamとか〜
on
のほかにはonce
というメーソドがある。on
と違って、once
はそのイベントを一回だけ受け取って、そのあと同じイベントが転送してきたら、無視するという挙動になる。
EventEmitterの応用
EventEmitter
を使う理由は、コードの可読性がよくなるだけじゃなく、同じイベントに複数の処理が行えるというメリットもある。例えば先ほどのサンプルで、DirReader
で読み出したデータをconsoleに出す同時に、データベースにも保存したい場合はどうするんだろう?もう一個on
を作ってdata
イベントを購読すれば良いのだ:
var reader = DirReader('./path');
reader.on('data', function(data){
// consoleにだす
console.log(data);
});
reader.on('data', function(data){
// データベースに入れる
var file = new File({body: data});
file.save();
});
reader.on('end', function(count){
console.log('ファイル' + count + '個を処理しました。');
});
reader.on('error', function(err){
console.log(err);
});
簡単でしょ?もちろん、consoleに出すなら、on
を一個用意するほどでもないが、ニュアンスが伝えられたらと思う。
まとめ
EventÉmitter
を使うことで、コードが読みやすくなるし、今後システムの拡張などにも十分な柔軟性を持つので、node.jsでの開発を次のレベルに向かうなら、ぜひEventEmitter
を活用してください。