💨

EventEmitterやpipe処理をasync関数で使いたい

2023/10/20に公開

このあいだPDFを作りたいときにpdfmakeというライブラリを使って血反吐を吐いた際、EventEmitter.on()地獄にハマって悲しくなり気持ちになったので、その際に使ったEventEmitterをPromise化してasync関数で使うという地味なテクでなんとかなったのでその時の状態をシェアします。

EventEmitter

Node.jsでは、ある処理を実行する際にEventEmitterを利用することがあります。この記事が詳しいので参照してください。今回特に見るのは something.on() や、 something.once() といったイベント登録です。
通常、この処理を利用する際は次のように書きます。

emitter.on('data', () => {
	// ...
})

この記法は現在でもたくさんのライブラリが採用しており、特にNode.jsで処理する場合は割りと避けられない記述です。
このemitterの処理を終わらせると、基本的にはこのemitterのcallback関数の中で処理を書いていかなければならないのですが、そうすると波動拳が発動し、インデントが爆発します。また、async/awaitとの相性が悪く、処理の順番が変になりがちです。

emitter.on('end', () => {
	something1("start", () => {
		something2("start", () => {
			console.log("callback")
		})
	})
})
console.log("normal") // こっちのほうが早く表示されるかも…

そこで、emitterをPromise化してしまいましょう。

await new Promise((resolve) => {
	let buffer = [];
	emitter.on('data', (buf) => {
		buffer.push(buf);
	});
	emitter.on('end', () => {
		resolve(buffer);
	});
	emitter.end()
});

このような具合です。これなら end のコールバック関数の中で resolve されるまでこのPromiseは待ってくれます。

同様に fs.createReadStream といったpipe処理も次のように処理できます。

await new Promise((resolve) => {
	fs.createReadStream('sample.txt')
		.pipe(crypto.createHash('sha256'))
		.on('finish', resolve);
})

Promise便利ですね。みなさんもPromiseでどんどん囲ってしまいましょう。終わり

Discussion