Operaでも非同期リクエストが並列処理できる img-JSONP

先日金床さんに教えてもらったOperaで非同期並列JSONPを実行させる方法を実際に試してみたら意外とすんなり動いてしまったので報告します。

最速インターフェース研究会 :: OperaでJSONPを非同期リクエストするより

JSONP が Opera だと非同期処理できない
http://d.hatena.ne.jp/secondlife/20060906/1157515075
に書かれているとおりOperaだとscript要素を足した瞬間にJavaScriptの実行が止まって、ロード完了まで後続のスクリプトが実行されなくなります。

サンプルコード

改善前(普通のJSONP)
Operaはスクリプトを追加するとタイマーが止まる(サンプルページ)
http://la.ma.la/misc/js/opera_jsonp_test.html
function test(){ 
        var s = document.createElement("script");
        var url = "http://del.icio.us/feeds/json/ma.la?";
        var now = new Date-0;
        url += "callback=c._"+now;
        c["_"+now]=function(obj){$("result").innerHTML=formatter(obj)};
        s.type = "text/javascript";
        s.charset = "utf-8";
        s.src = url;
        document.body.appendChild(s);
}
改善後(img + JSONP)
Opera img + JSONP(サンプルページ)
http://namazu.org/~takesako/opera/img_jsonp.html
function test(){ 
        var url = "http://del.icio.us/feeds/json/ma.la?";
        var now = new Date-0;
        url += "callback=c._"+now;
        c["_"+now]=function(obj){$("result").innerHTML=formatter(obj)};

        var img = document.createElement("img");
        img.onerror = function(e){
                var s = document.createElement("script");
                s.type = "text/javascript";
                s.charset = "utf-8";
                s.src = url;
                document.body.appendChild(s);
        };
        img.width = 0;
        img.height = 0;
        img.src = url;
        document.body.appendChild(img);
}

↑ ma.laさんのコードをそのままコピペしてここだけ修正しています。

■ 動作原理

IMGとして取得させキャッシュに入れる方法(金床さんのコメント)より:

JSONPのリソースを一度IMGタグから取得させてブラウザのキャッシュに入れておき、その後SCRIPTの要素として同じURLを再取得する、という方法が使えるかもしれません。

1.JSONPのリソースをIMGタグの追加などで取得する(このとき、あらかじめエラーハンドラを別の関数に設定しておく)
2.画像ではないのでエラーになる
3.エラーのイベントハンドラが呼び出されたらSCRIPTタグの追加で再度同じリソースを取得する
4.このときレスポンスは既にキャッシュに入っているため、一瞬で取得が終わる(限りなく非同期ぽい同期処理)

JSONPのレスポンスヘッダにPragma: no-cacheなどの指定があると動かない可能性が高いです。

非同期リクエストをimgタグで飛ばして、結果をonerrorハンドラで受けとって、ブラウザのキャッシュを再利用するという方法。

各ブラウザでの動作

HTTP Proxy を間に入れて、img + JSONP のHTTPリクエストを観測してみました。

Firefox2の場合

User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.8.1.3) Gecko/20070309 Firefox/2.0.0.3

img_jsonp_firefox.gif

Firefoxだと、1回目のimgタグ生成時と、2回目のscriptタグ生成時の両方で2回リクエストが飛んでしまいます。なので動作にもっさり感があるし、サーバに2倍負荷がかかるので、あまりお勧めできません。

IE6の場合

User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)

img_jsonp_ie.gif

IEだと2回目のリクエストに
If-Modified-Since: Fri, 13 Oct 2006 08:28:38 GMT; length=3277
のようなヘッダをつけてくれるので、サーバ側が対応していれば304のステータスを返してくれます。

Opera9の場合

User-Agent: Opera/9.01 (Windows NT 5.1; U; ja)

img_jsonp_opera.gif

Operaの場合は、1回目のimgタグ生成時に保存されたブラウザのキャッシュが活用され、2回目のリクエストは発生しなくなります。
このときimgタグの非同期リクエストの処理がタイマーを停止することなく並列に行なえるので一石二鳥です。

まとめ

金床さんのJavaScriptのonerrorハンドラを活用する発想には目から鱗で感服しました。

OperaでのJSONP+Comet問題に応用できるかどうかはまだ試していませんが、ブラウザ判別の処理を入れて試してみる価値はあるかもしれません。

いまだに新しい発見のあるJavaScriptはこれだから面白いです。

コメントは受け付けていません。