はじめに
この記事は Node.js Advent Calendar 2019 の 24 日目です。
https://twitter.com/yosuke_furukawa/status/1201778011286065153
Yosuke FURUKAWA @yosuke_furukawa
なんだこの誕生日アドベントカレンダーは / “Node.js Advent Calendar 2019 - Qiita”
午後5:19 · 2019年12月3日·はてなブックマーク
最初は誕生日カレンダーみたいになってたので、ネタ記事を書こうかなーと思っていました。
ですが、始まってみると案外真面目な記事ばかりだったので、真面目に書くことにしました。
(そう、私は12/24生まれです!{クリスマス,誕生日}{ケーキ,プレゼント}はいつも一つ。)
StreamでBuffer.concatしない冴えたやりかた
一般的にStreamを扱う時は on("data", ...)
で処理することが多いと思います。
しかし、実際に使うサイズと流れてくるサイズは一致しないことが多く…
const net = require('net');
function onRecv(packet){ /* ... */ }
const server = net.createServer(conn => {
let receivedQueue = Buffer.alloc(0);
conn.on('data', chunk => {
let data = Buffer.concat([receivedQueue, chunk]);
//40バイトずつ読み出したい
while(data.length >= 40){
onRecv(data.slice(0, 40));
data = data.slice(40);
}
receivedQueue = data;
});
}).listen(3000);
のような処理になりがちです。…なんだか冗長に感じませんか?
他の言語では読み出し可能バイト数を取得、指定バイト以上なら一気に読み出す…みたいな処理を書くことが多いと思います。Arduinoのシリアル通信とか。
Node.jsで同じような処理は書けないのか?書けます。
Node.jsには、任意のバイト数を読み出すための readable
イベントが存在します。
しかし、以前までは "読み出し可能なバイト数" を取得する方法が
stream._readableState.length
しかありませんでした。_
が気になりますね…
でも今は大丈夫!
v9.4.0から、これを取得するための readableLength
プロパティが追加されています。
stream.readableLength
罪悪感(?)が無くなりましたね。
これを使うと、先程のコードを以下のように書くことができます:
const net = require('net');
function onRecv(packet){ /* ... */ }
const server = net.createServer(conn => {
conn.on('readable', () => {
while(conn.readableLength >= 40){
onRecv(conn.read(40));
}
});
}).listen(3000);
ああ…気持ちいい!これが書きたかった。これが求めていたもの・・・
コードが綺麗になることは勿論、Buffer.concatはデータの複製を行うので、処理速度の改善も期待できます。
このパターンが使える場面では積極的にreadableイベントを使っていきましょう。
注意点
この方法で読み込める最大バイト数は highWaterMark
までになります。
大きなデータをまとめて取得したい時は、 highWaterMark
の上限値を拡大する必要があります。
※ただ、デフォルトの highWaterMark
を超えるデータを一気に処理する処理自体あまり良くないので、少しずつ処理できるようにしたほうがいいかもしれません。
p.s. highWaterMark
は、シンプルに説明すると他言語のbufferSizeみたいなものです。(実際にはもっとイケてる実装の一部だったりしますが、とりあえずはそんなものだと思えば十分。)
さいごに
あまりreadableイベントを使った記事を見かけないので書いてみました。お試しあれ。
おわりー。