継続を使って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パターンやイベントキューを検討してみたが、コーディングが面倒そうだし、コードも読み難くなりそうだった。そこで、継続を使ってみたところ、簡単にSjaxをAjaxに変換できた。
先ほどの例を、継続を使って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); }
継続を始めて知った時には、あまり役に立つ概念とは思えなかったけれど、やはり有名な概念は学んでおいて損はないと思った。