JSDeferredで,面倒な非同期処理とサヨナラ

第3回 JSDeferredを用いたアプリケーション開発(その2)

この記事を読むのに必要な時間:およそ 0.5 分

前回に引き続き,Wikipediaから緯度経度を取得しGoogle Maps上にプロットするアプリケーションを作っていきます。

今回は透過的なキャッシュの仕組みを入れるのと,geolocation APIを用いて,データのロードが終わり次第自分の近くのマーカーを表示させてみたいと思います。

完成コードは以下のようになります。

透過的なキャッシュ

キャッシュのための領域として,localStorageというものを使います。これはFirefoxやSafariなどで実装済みの,簡単にいってしまえば大容量のCookieみたいなものです。

localStorageは同期的に値を取得やセットをできますが,キャッシュするデータはXMLHttpRequestで取得される非同期なデータです。この二つの条件分けを最小限にして実装するために,JSDeferredを間に挟むことにします。

前回は,WikipediaからJSONのデータを取得するために,以下のようなコードを記述しました。

リスト1

$.getJSON(url).
next(function (data) {
   // ...
});

はじめに,最終的にどうしたいのかを考えます。この場合,キャッシュはあってもなくても最終的にやることは変わらないようにしたいため,getJSONのあたりで何もかもを終わらせてやりたいですね。そこで,以下のようなコードになることを目標にしてみます。

リスト2

withCache('wikipediajson', function () { return $.getJSON(url) }).
next(function (data) {
   // ...
});

withCache(key,fun);はkeyをキャッシュキーにしてfunが返すデータをキャッシュする関数で,Deferredオブジェクトを返します。このような関数を作れれば,他の部分でキャッシュしたいときも同じようなコードでいくらでもキャッシュできそうですね。

withCache関数を作るために,Deferredオブジェクトを自分で作ってみることにします。Deferredオブジェクトを自分で作る場合は最終的に,非同期処理の中でDeferredオブジェクトのcall()メソッドを呼びだすようにします。簡単な例を示すと以下のようになります。

リスト3

function foobar () {
    var d = new Deferred;
    setTimeout(function () { d.call('done!') }, 0);
    return d;
}

foobar().next(function (r) {
    alert(r); //=> 'done!'
});

これでDeferredオブジェクトを返す関数を作ることができます。

withCacheでは,キャッシュのあるなしによって条件分岐して,最終的には上記コードのようにcall()メソッドを呼びます。まずはコードをご覧ください。

リスト4

function withCache (key, fun) {
    var d = new Deferred();

    var val = localStorage.getItem(key);
    if (val) {
        next(function () {
            d.call(eval(val));
        });
    } else {
        fun().next(function (data) {
            try {
                localStorage.setItem(key, uneval(data));
            } catch (e) {
                localStorage.clear(); // キャッシュが溢れた
                localStorage.setItem(key, uneval(data));
            }
            d.call(data);
        });
    }

    return d;
}

localStorageにデータがあるときはnext()関数で呼び出しを非同期化した上でcall()メソッドを呼んでいます。JavaScriptで非同期処理を同期化することは決してできないのは先に言及したとおりですが,同期処理を非同期処理に変換するのはできるので,非同期の流儀にあわせてあげることで非同期処理と同期処理を混在させることができます。

この同期処理をnext()で非同期にするということをせずに,d.call()を呼んでしまうと,この時点ではまだ作られたDeferredオブジェクトは「次にすべき処理」を知らないため,何も起こりません。⁠次にすべき処理」はこの関数がreturnされたあとにreturnされたDeferredオブジェクトのnext()メソッドを呼ぶことで設定されます。そのため,そのあとにd.call()を呼ぶようにしてあげる,というわけです。

localStorageにデータがないときは,渡されたfunを実行し,返ってきたデータをlocalStorageに設定して,最後はデータがあるときと同じようにcall()メソッドを呼んでいます。

著者プロフィール

cho45(さとう)

はてなエンジニア。サブテカ。バックエンドからインターフェイスまで,Perl,Ruby,JavaScript,ActionScriptなどを使いつつ Scala,Ioなどを触る,言語・コード表現ヲタク。

URLhttp://www.lowreal.net/
技術ネタhttp://subtech.g.hatena.ne.jp/cho45/

コメント

コメントの記入