hiromitsuuuuu.log();

to see more to see.

jQueryのDeferredをかじってみた

設定系のJSONやら素材やらをXHRで複数読み込むのはいいものの、思うような順番で非同期処理を実行しようとするとなんか超入れ子になる!これ、なんとかならんもんかなぁと思ってたんですが、jQueryのDeferredなるものが使えそうなのでかじってみた。

今から一斉にみんな走ってー!そんでこの人達が終わるまで待ってー!この人達が終わったら次君ねーとかやるやつ。擬似的なマルチスレッドぽいことができる。

やりたいこと

1.複数JSONのファイル名が書かれている元となるJSON、setting.jsonを読み込む
2.setting.jsonに書かれている複数JSONを同時に読み込む
3.2の読み込みが全部終わってからなんかする

元となるJSONはこんな感じ。今回使うのはcharactersの配列のみ。

setting.json
{
    "characters": ["cat", "dog", "panda"],
    "words": ["good morning","hello","good night"],
    "term": 300
}

charactersに入ってる名前ごとに下のようなJSONファイルを用意してみた。この複数のファイルを同時に読み込んで、読み込みが全部終わったら次の作業をしたいとする。

cat.json
{
    "name": "tom",
    "age": 6,
    "sex": "male"
}

setting.jsonを読み込む

Ajaxでsetting.jsonを読み込んで、読み込みが終わったらファイルパスが入った配列を生成する。resolve()は任意の引数を受け取る事ができ、後続関数に引数を渡せるので、then()の中で使うためにファイルパスが入った配列を引数として渡してやる。

    function loadSetting() {
        var d = $.Deferred();        // Deferredオブジェクトを作る
        $.ajax({
            url : 'js/setting.json',
            dataType : 'json',
            success : function(data) {
                var nextFileNameArr = getNextFileNameArr(data.characters);
                d.resolve(nextFileNameArr);         // 後続関数に値を受け渡す
            }
        });
        return d.promise();        // promiseオブジェクトを返す
    }

    loadSetting().then(function(nextFileNameArr) {
        // 受け取った nextFileNameArr を使ってなんかする
    });

ここまではsuccessのコールバックとして書くのとあんまりかわんない。

複数の非同期処理を待ってから次の処理を行う

次に、引数で渡されたファイルパスのJSONの読み込み処理を実行して、すべての読み込みが終わるまで待つ。引数で渡した処理がすべて終了するまで待ってくれるのがwhen。引数の処理すべてが成功したときだけ(resolvedのときだけ)doneに移る。whenの使い方はこんな感じ。

    function a(time){
        var d = $.Deferred();
        setTimeout(function(){
            console.log(time);
            d.resolve();
        },time);
        return d.promise();
    }

    $.when(a(2000),a(3000),a(4000)).done(function(){
        console.log('all done');
    });

whenの引数に渡した処理が全部終わってから、次の処理に移ってるのがわかる。
f:id:hiromitsuuuuu:20120623182939p:plain

whenにapplyを使って引数を渡す

上みたいにwhenの引数として、JSONをロードする関数を地道に書いてあげれば動くんだけど、読み込み対象のファイルが増減することも考えて、関数を動的に作ってwhenの引数として渡してあげる。

    // 個別にJSONをロードする関数
    function load(nextFile) {
        var d = $.Deferred();
        $.ajax({
            url : nextFile,
            dataType : 'json',
            success : function(data) {
                console.log("data", data);
                d.resolve();
            }
        });
        return d.promise();
    }

    // Deferredの実行部分。thenで引数を受け取って関数を生成。
    loadSetting().then(function(nextFileNameArr) {

        var loadMethods = [];
        var length = nextFileNameArr.length;
        for ( var i = 0; i < length; i++) {
            // whenに渡したい関数を生成
            var loadMethod = load(nextFileNameArr[i]);
            loadMethods.push(loadMethod);
        }

        // whenに引数をapplyで渡す。
        $.when.apply($, loadMethods).done(function() {
            console.log('all done');
        });

    });

    $.when.apply(null, loadMethods).done(function() {
        console.log('all done');
    });

Deferredには直接関係ないけど、whenに引数を渡すためにapplyを使った。引数が配列なのがapply。同じようなのでcallっていうのがあるけど、こっちはカンマ区切りで個別に引数を渡すとこが違う。第一引数にはthisにしたいオブジェクトを渡す。jQueryのwhenの中ではthisを使ってないようなので、第一引数はnullで大丈夫っぽい。callとapplyの第一引数に何を渡すかすぐパニックになるのでもうちょっと理解したいなぁ(´・ω・`)

以上のコードを動かしてみると、コンソールはこんな感じになる。

f:id:hiromitsuuuuu:20120623180713p:plain

というわけで、なんとかやりたいことの1~3ができた!今回は成功時の処理しか書いてないけど、失敗時の処理とかも書けるみたい。非同期処理をたくさん走らせるときにDeferred便利そうヽ(●´w`○)ノ