継続を使ってSjaxをAjaxに簡単に変換する方法

JavaScriptによる全文検索エンジンの最初のバージョンはAjaxではなく、Sjaxであった。その為、サーバへのリクエストが発生する毎にブラウザが固まってしまい、応答性が悪かった。なぜ、Sjaxで記述したかというと、連続してサーバへリクエストを送り、しかも、サーバからのレスポンスに応じてリクエストを変更するようなAjaxプログラミングが面倒だった為である。このようなSjaxのコード例を次に示す(prototype.jsを使用)。

// (Sjax)サーバからpathのデータをoffsetの位置からlengthバイト取得
function fetch(path, length, offset){
    var range = ["bytes=" + offset + "-" + (offset + length - 1)].join("");
    var options = {
	method: "get",
	asynchronous: false,
	requestHeaders: ["Range", range]
    }
    var request = new Ajax.Request(path, options);
    return request.transport.responseText;
}
function fetch_data(){
    var path = "/data.txt";
    // 先頭4byteからoffsetを取得
    var offset = parseInt(fetch(path, 4, 0));
    // 先頭4byteからlengthを取得
    var length = parseInt(fetch(path, 4, 4));
    // 取得したoffset, lengthのデータを取得し返却
    return fetch(path, length, offset);
}
alert(fetch_data);

このようなSjaxによる処理をAjaxに変更する際、どうすれば簡単に書けるかしばらく考えていた。Stateパターンやイベントキューを検討してみたが、コーディングが面倒そうだし、コードも読み難くなりそうだった。そこで、継続を使ってみたところ、簡単にSjaxAjaxに変換できた。

先ほどの例を、継続を使ってAjax化すると次のようになる。

// (Ajax)サーバからpathのデータをoffsetの位置からlengthバイト取得し、取得後contを実行。
function fetch(path, length, offset, cont){
    var range = ["bytes=" + offset + "-" + (offset + length - 1)].join("");
    var options = {
	method: "get",
	onComplete: function(request){cont(request.responseText);},
	requestHeaders: ["Range", range]
    }
    new Ajax.Request(path, options);
}
function fetch_data(cont){
    var path = "/data.txt";
    fetch(path, 4, 0, function(offset){
	offset = parseInt(offset);
	fetch(path, 4, 4, function(length){
	    length = parseInt(length);
	    fetch(path, length, offset, function(data){
		cont(data);
	    });
	});
    });
}
fecth_data(alert);

継続を使う方法のメリットには次の3点がある。

  • 処理の流れとコードの流れが一致する。
  • 単純作業でコードが変換できる。
  • apiの変更が少ない。

関数呼び出しのネストが深くなるのが少々難点だが、たいしたデメリットではないと思う。実際に、継続を使って名古屋市で賢い借金返済方法を教えます!Ajax化してみたが、予想より短時間で実装できた。

なお、継続でループを書く場合には、再帰呼び出しになる。例えば、次のSjaxコード

function loop_fetch(l){
    var data = "";
    for(var i = 0; i < l; i ++){
	data += fetch("/data.txt", 4, 4 * i);
    }
    return data;
}

を継続を使ってAjaxにすると、次のようになる。

function loop_fetch(l, cont){
    var data = "";
    var f = function(i){
	if(i < l){
	    fetch("/data.txt", 4, 4 * i, function(rv){
		data += rv;
		f(i + 1);
	    });
	}else{
	    cont(data);
	}
    }
    f(0);
}

継続を始めて知った時には、あまり役に立つ概念とは思えなかったけれど、やはり有名な概念は学んでおいて損はないと思った。