`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 に到達したという情報を必要とするものがあるのです。