TransformStream の出力側のストリームは明示的に閉じる
`TransformStream` は `ReadableStream` と `WritableStream` の両方を持っています。名前の Read と Write はストリームの使用者(consumer)から見たものなので、実装する側から見ると逆になって直感的ではないのですが、`WritableStream` が前段のストリームからのデータを受け取り、`ReadableStream` は後段のストリームにデータを受け渡します。
前段のストリームのデータの受け渡しが終了すると、`TransformStream` で前段からの受け取りに使用している `WritableStream` は自動的に閉じられますが、後段への受け渡しに使用している `ReadableStream` は開いたままです。`TransformStream` 側で、明示的に閉じる必要があります。
次の例を見ましょう。シンプルな `TransformStream` です。input.txt を読み込み、output.txt.gz を出力します。
"use strict";
const fs = require("fs"),
stream = require("stream"),
zlib = require("zlib");
const transform = new stream.Transform({
transform: function (chunk, encoding, callback) {
// your code here
this.push(chunk);
callback();
},
flush: function (done) {
this.push(null); // notify EOF
done();
}
});
const reader = fs.createReadStream("input.txt"),
gzip = zlib.createGzip(),
writer = fs.createWriteStream("output.txt.gz");
reader.pipe(transform).pipe(gzip).pipe(writer);
メソッド `transform` には、ストリームとして渡されたデータの変換処理を書きますが、ここではそれは本題ではありませんので割愛します。上のコードでは、受け取ったデータを無加工でそのまま出力します。
メソッド `flush` で `this.push(null);` を実行し、自身より後段に接続されているストリームに対して、データが終了したことを知らせます。この処理を忘れると、上の例の場合は次のような結果になります。上のコードを実行した後に、アーカイブの解凍を試みます。
$ gunzip output.txt.gz gunzip: output.txt.gz: unexpected end of file gunzip: output.txt.gz: uncompress failed $
gzip のストリームが正常に閉じらなかったため、壊れたアーカイブができあがってしまいました。この現象を体験するために、是非一度、上のコードの`this.push(null);` をコメントアウトして、試してみてください。
ストリームは再利用しやすいオブジェクトです。再利用時の予期せぬバグを防ぐためにも、`this.push(null);` を忘れないようにしましょう。あなたが実装した `TransformStream` の後段に接続されるストリームには、EOF に到達したという情報を必要とするものがあるのです。