nsIInputStreamPumpの非同期処理にジェネレータを使う

こいつの使いかたをググると、onDataAvailableのたびに配列(メッセージキュー)に並べておいて、上から順番に処理するコードがよく見つかる。

実はAjaxのとき話題になったんだが、ジェネレータで、こういった非同期処理を同期処理のように書ける。

この実験のために、いわゆるechoサーバをlocalhostの60000番ポートに立ててある。

"yield" == "onDataAvailableまでsleep".

let p = Application.console.log;

function nettest() {
  //ソケット(トランスポート)を作る
  let TransportService = Cc["@mozilla.org/network/socket-transport-service;1"].
                           getService(Ci.nsISocketTransportService);
  let transport = TransportService.createTransport(null, 0, "localhost", 60000, null);
  let line = {};
  //非同期処理にジェネレータを渡す。
  callWithClientSocket(transport, function(cistream, costream) {
                          //サーバに"hello\n"を送って、
                          costream.writeString("hello\n");
                          //待つ。
                          yield;
                          //サーバから返事が来たら1行読む。
                          cistream.readLine(line);
                          //コンソールにプリント。
                          p(line.value);
                          //サーバに"world\n"を送って、
                          costream.writeString("world\n");
                          //待つ。
                          yield;
                          //サーバから返事が来たら1行読む。
                          cistream.readLine(line);
                          p(line.value);
                          //サーバに"こんにちは\nせかい"を送って(UTF-8でおk)
                          costream.writeString("こんにちは\n");
                          costream.writeString("せかい");
                          //待つ.
                          yield;

                          cistream.readLine(line);
                          p(line.value);
                          cistream.readLine(line);
                          p(line.value);
                          yield;
                        });
}

function socketInputStream(aTransport) {
  //ソケットの入力ストリームをとる
  let istream = aTransport.openInputStream(0, 0, 0);

  //ソケットの入力ストリームの文字コードをUTF-8にする
  let cistream = Cc["@mozilla.org/intl/converter-input-stream;1"].
                   createInstance(Ci.nsIConverterInputStream);
  cistream.init(istream, "UTF-8", 0, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
  //ソケットの入力ストリームを行単位で読むようにする
  cistream.QueryInterface(Ci.nsIUnicharLineInputStream);

  //ソケットの入力ストリームの非同期ポンプをとる
  let pump = Cc["@mozilla.org/network/input-stream-pump;1"].
               createInstance(Ci.nsIInputStreamPump);
  pump.init(istream, -1, -1, 0, 0, false);

  return [pump, cistream];
}

function socketOutputStream(aTransport) {
  //ソケットの出力ストリームをとる
  let ostream = aTransport.openOutputStream(0, 0, 0);
  //ソケットの入力ストリームの文字コードをUTF-8にする
  let costream = Cc["@mozilla.org/intl/converter-output-stream;1"].
                   createInstance(Ci.nsIConverterOutputStream);
  costream.init(ostream, "UTF-8", 0, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);

  return costream;
}

function callWithClientSocket(aTransport, aProc) {
  //ソケットの入出力ストリームと、非同期ポンプをとる。
  let [pump, cistream] = socketInputStream(aTransport);
  let costream         = socketOutputStream(aTransport);

  // Javascript 1.7 の新機能、ジェネレータを作る。
  let generator = aProc(cistream, costream);

  //非同期ポンプのリスナ
  let listener = {
    generator: generator,  //onDataAvailableでの"環境"はちょっと特殊。
    transport: aTransport, //onDataAvailableでの"環境"はちょっと特殊。
    onStartRequest: function onStartRequest(channel, context) {
    },
    onStopRequest: function onStopRequest(channel, context, status, errorMsg) {
      this.generator.close();
      this.transport.close(null);
    },
    onDataAvailable: function onDataAvailable(channel, socketContext, inputStream, sourceOffset, count) {
      //非同期ポンプにデータが来るたびに、ジェネレータを進める。
      this.generator.next();
    }
  };
  generator.next(); //こいつをonStartRequestに書くと動かないのは、なんで?
  pump.asyncRead(listener, null);
}

小ネタ

  • Firefox3拡張を書くなら、もうvarは捨ててletでいいとのこと(id:nanto_viさんに教えてもらった。thx.)。C言語構文の皮をかぶったSchemeの世界征服計画が進んでいるぜ。ふっふっふ。
  • echoサーバはGaucheユーザリファレンス: gauche.selector - 簡単なディスパッチャからパクった。
  • 最近は、XPCOMのIOやソケットの、Gauche風のラッパーを書きためている。callWithClientSocketという関数名もGaucheからパクった。
  • Ajaxにジェネレータを使うのは有名だけど、"nsIInputStreamPump yield"でググっても出てこない。既出だったら教えてください。