nodeとstream周り、ジェネレータ、非同期処理

最近調べたnode.jsのstreamに関連した雑多な内容を、思いつくままに適当に書く。

例えば、

function* createPiGenerator() {
  var result = 0;
  for (var i = 1;; i += 4) {
    yield result * 4;
    result += 1/i - 1/(i + 2);
  }
}

こんな感じのジェネレータがあったとして、それをファイルに出力したい場合

function createPiStream() {
  var r = new require('stream').Readable();
  r.setEncoding('UTF-8'); // バッファじゃなくてstringとして処理したい
  var g = createPiGenerator(); // piの計算をするジェネレータ
  var tid;
  r._read = function () { // size指定は無視して良い
    if (!tid) {
      tid = setInterval(function () {
        r.push(g.next().value + '\n'); // gは無限に出力できるからチェックは無し
      }, 50);

    }
  };
  r.on('end', function () {
    clearInterval(tid); // 終了時はもうpushできないから消す
    tid = 0;
  });
  return r;

}

こういう風にstreamを作っておくと

createPiStream().pipe(process.stdout);

こんな感じに使えて、この場合は出力するだけだけど、例えばファイルに出力したりとか、そういうのが簡単になる。

ところで、StreamにはobjectModeというのがあって、それを使うと文字列に直さなくても、numberとかobjectとかをそのままデータとしてstreamに流せるようになる。Node.js v0.10.28 Manual & Documentation を参照。

全く別の話で、Gruntみたいなtask runnerで、gulp.jsというのがあって、これはstreamを利用することで、中間ファイルを作らずに、minifyだとか、JSのlintだとか、concatだとの処理を、pipeでつなげて効率的に行うことができて、最近はGruntよりもgulp.jsのほうが簡潔でオシャレで、書きやすいのでおすすめである。

gulp.task('default', function () {
  gulp.src('src/*.js')
  .pipe(jshint())
  .pipe(uglify())
  .pipe(gulp.dest('build'))
});

例えばこんな感じにして

$ gulp

とするだけでbuildフォルダ内に処理されたものが作られたり。良いものである。

最近のStreamまわりの話題は、Stream2, Stream3, readableとかで検索するとでてきて、 stream-handbookとか、through2とか、event-streamとかを読むとまあまあ便利さがわかる。

node.jsでの非同期処理はStreamとか、Promiseとか、EventEmitterとか、thunkとか、callbackとかが色々あるけど、きれいに書ける場面がわりと分かれているので、最近の何でもPromiseみたいな風潮に惑わされないようにしたい。 最近おすすめの非同期ライブラリはAsyncで、本格的にはまだ使ってないけど、EventEmitterをベースにした有限オートマトンとか、非同期のロック、バッファ付きのemitterなどがあって、なかなかAPIも簡潔なので、良い。

連絡は@javascripterskype:javascripter_まで。