クロージャを使って、インターフェースを合わせこむ

テーマ

公開済みの関数

以下の、Ajaxなラッパー関数があったとしましょう。

function ajax(url, callback) {
  var xhr = uuw.XMLHttpRequest ? new XMLHttpRequest()
          : uuw.ActiveXObject  ? new ActiveXObject('Microsoft.XMLHTTP') : null;

  xhr.onreadystatechange = function() { ... callback(); ... }
  xhr.open("GET", url, true); // async
  xhr.send(null);
  return xhr;
}

XMLHttpRequestオブジェクトが戻されるので、リクエストの中断はこう書けます。

var xhr = ajax("http://example.com/", function() { alert("done"); });
xhr.abort();

公開済みの関数にI/Fを合わせる

仮にあなたがライブラリ作者だとして、

1. AjaxJSONPって、出来ることが似てるなぁ
2. AjaxがダメならJSONPで代行すれば使いやすくなるよね?
3. よし、JSONPをラップする関数を作ってみよう。I/Fは、既存のajax()のソレに合わせてみよう
4. あれ? ajax()は“XMLHttpRequestオブジェクトを返す”ってI/Fだったね。
5. I/Fレベルで違いすぎるのはダメだし、ajax()のI/Fを変更するのは本末転倒だ。どうしよう?

function jsonp(url, callback) {
  var head = document.documentElement.firstChild;
  var e = document.createElement("script");

  jsonp.fn = function(text) {
    callback(text);
  };

  e.type = "text/javascript";
  e.charset = "utf-8";
  e.src = url + "&callback=jsonp.fn";
  head.appendChild(e);
}

このようなコードを実装した時点でライブラリ作者はハタと困ります。
どうすれば適切なオブジェクトを返し、リクエストの中断機能をJSONPでも提供できるのか?

ここ大事

jsonp()に数行追加すればabort()が可能になります。

  // ajax()とI/Fを合わせるためにクロージャなオブジェクトを返しabort()を可能にする
  function F() {};
  F.prototype.abort = function() {
    e.src = "";
    head.removeChild(e);
  };
  return new F();

こんな感じに。

var jp = jsonp("http://example.com/.../...?hoge=huga", function() { alert("done"); });
jp.abort();

テストコード

<html>
<head>
  <title>uupaa's diary</title>
</head>
<body>
<script>
function jsonp(url, callback) {
  var head = document.documentElement.firstChild;
  var e = document.createElement("script");

  jsonp.fn = function(text) {
    callback(text);
  };

  e.type = "text/javascript";
  e.charset = "utf-8";
  e.src = url + "&callback=jsonp.fn";
  head.appendChild(e);

  function F() {};
  F.prototype.abort = function() {
    e.src = "";
    head.removeChild(e);
  };
  return new F();
}
</script>

<script>
var targetURL = "http://d.hatena.ne.jp/uupaa/20080420/1208636929";
var url = "http://b.hatena.ne.jp/entry/json/?url=" + encodeURIComponent(targetURL);
var jp = jsonp(url, function(obj) { alert("title:" + obj.title); });
jp.abort();
</script>

</body>
</html>

反省会

  • ajax()の実装(オブジェクトを返す)が、そもそもアリなのかと。
    • ものの例えであってだね、AjaxJSONPの例じゃなくても何でも良かったのよ。視点がズレてますよ。
  • テストコードって動くの?
    • IE6,Safari3.1,Opera9.5β2ではabortしてる。FirefoxFirebugでステップ実行すれば動作は見れた。
      • 試した環境だと、ハテナからすぐにレスポンスがあるのでFirefoxではabortできなかった。
    • あと、上記のajax()とjsonp()って、説明用に実用性を削ってあるから、参考にするのはまずい。
  • こういう例でクロージャを使うのって誤解されない? 「I/Fを後から拡張できるのはケシカラン」みたいな。
    • 「若い娘が肌を露出させるのはケシカラン」ってのと同じニュアンスですね。わかります。