JSONPのサポート

uupaa.jsJSONPを実装してみました。

/* uupaa.js (C) uupaa */
var uud = document, uuw = window, uu;
uud.head = uud.documentElement.firstChild; // document.head

new function() {
  uuw.uu = {
    work: {
      id: { jsonp: 0 } // unique id
    },
    jsonp: {}, // JSONP callback function holder

    uniqueID: function(key /* = "jsonp" */) {
      var id = this.work.id, k = key || "jsonp";
      return (k in id) ? ++id[k] : (id[k] = 1);
    },
    mix: function(base, flavor) {
      for (var p in flavor) { base[p] = flavor[p]; }
      return base;
    },
    id: function(id) {
      return uu.idCache[id] || (uu.idCache[id] = uud.getElementById(id));
    },
    idCache: {},
     /** JSONP request
     *
     * @param string    url               request url fragment. "http://example.com/cgi?key=value"
     * @param function  fn                callback function
     * @param object    bindThis          バインドするthisを指定します。
     * @param string    callbackKeyword   コールバック関数名を変更する場合に指定します。デフォルトは"callback"です。
     */
    loadJSONP: function(url, fn, bindThis /* = undefined */, callbackKeyword /* = "callback" */) {
      var uid = uu.uniqueID("jsonp"), cbkey = callbackKeyword || "callback";

      // add callback holder function
      uu.jsonp["_" + uid] = function(json) {
        fn.call(bindThis, json);
        uuw.setTimeout(function() {
          uud.head.removeChild(uu.id("uu_jsonp_" + uid)); // remove <script id="uu_jsonp_{uid} ...>
          delete uu.jsonp["_" + uid]; // suicide
        }, 10);
      };
      // add <script id="uu_jsonp_{uid}" src="{url}&callback=uu.jsonp._{uid}"></script>
      uud.head.appendChild(uu.mix(uud.createElement("script"), {
        id: "uu_jsonp_" + uid,
        type: "text/javascript",
        charset: "utf-8"
      })).src = url + (url.lastIndexOf("?") === -1 ? "?" : "&") + cbkey + "=uu.jsonp._" + uid;
    }
  };
};

はてなブックマークJSONPで取得するには、はてなブックマークエントリー情報取得API を参考に、こんな感じにします。

  function hatebu(mix) {
    alert(mix.title); // タイトル
    alert(mix.count); // ブックマークしている合計ユーザ数
  }
  var url = "http://d.hatena.ne.jp/uupaa/20080420/1208636929";
  var jsonpURL = "http://b.hatena.ne.jp/entry/json/?url=" + encodeURIComponent(url);
  uu.loadJSONP(jsonpURL, hatebu);

しくみ

 uu.jsonp._連番() { JSONな文字列; };
  • といったレスポンスが返ってきます。
  • レスポンスの内容がJavaScriptコードとして解釈され、callback=で指定したコールバック関数(uu.jsonp._連番)が呼ばれます。
  • uu.loadJSONP()の引数fnに指定した関数(hatebu)は、uu.jsonp._連番 関数内から間接的にコールされます。
  • uu.jsonp._連番 関数は、head内に追加したscriptタグの削除や、uu.jsonp._連番 関数の後処理を行います。


反省会

  • XMLHttpRequest との住み分けがいまいち
  • 良いところ
    • 後始末を自分でやる
    • コールバック関数の名前をネットワークにさらさないように(さらすのは、uu.jsonp._連番 という名前)
    • fn.call(this, json)とやりたい時もあるかもしれないので、thisを渡せるようにしてある。
  • その他
    • document.head がどうしてもほしいので勝手に追加。地味に便利。