jQuery Deferred 基礎の基礎

 JavaScriptは、シングルプロセスながらイベントによる割り込みを行うことで、非同期的な処理を可能とした言語です。

 今回は、この非同期処理を扱う上で避けて通れない「コールバック地獄」と、対応策のひとつであるjQuery Deferredを主軸に解説を行いたいと思います。

スポンサーリンク
rectangle

コールバック地獄

 Webアプリケーションでは、非同期的な処理を行い、完了後また続きの処理を呼び出し、またそれが完了したら・・・と、非同期処理を行いたいが中身は同期的な処理にしたいというケースはいくらでもあります。

 例えば、ajaxの結果をもって次のajaxを呼び出すようなコードを考えてみます。

$.ajax({
    url: "//www,example.com",
    success: function(data){
        $.ajax({
            url: "//www2.example.com",
            data: data,
            success: function(data2){
                $.ajax({
                url: "//www3.example.com",
                data: data2,
                success: function(data3){
                    .
                    .
                    .
                }
            }
        })
    }
});

 ネストが深く、とても読みづらいですね。プログラミングの至上命題は、「読みやすく」書くことですから、上記の例は非常によくないことがわかります。

jQuery Deferred

 そこで使えるのがjQuery Deferredという非同期管理オブジェクトです。実は、先ほど例に挙げたajaxにも組み込まれていて、$.ajaxの戻り値としてDeferredのPromiseオブジェクトが返ってきます。

Promiseオブジェクトとは

 then、done、fail、alwaysなどのメソッドを持ったオブジェクトで、各イベントに対する処理の定義ができます。先ほどの例は、以下のようにも書けます。

$.ajax({
    url: "//www.example.com"
    }
}).then(function(data){
    return $.ajax({
        url: "//www2.example.com",
        data: data
    });
}).then(function(data){
    return $.ajax({
        url: "//www3.example.com",
        data: data
    });
}).then(function(data){
    .
    .
    .
});

 ネストが浅くなり、読みやすくなりました。

Deferredの使い方

 実は、このajaxで返ってくるPromiseオブジェクトですが、自作関数などに利用することもできます。

 試しに、ajaxをラップして、返ってきた文字列をbodyにappendする処理を加えた関数を作ってみましょう。

// 関数定義
function ajaxWithAppendToBody(ajaxOptions){
    var deferred = new $.Deferred;
    $.ajax(ajaxOptions).done(function(data){
        $(data).appendTo("body"); // ajax通信が成功したらbodyにappendする。
        deferred.resolve(data);
    }).fail(function(data){
        deferred.reject(data);
    });
    return deferred.promise();
}
// 利用側
ajaxWithAppendToBody({
    url: "//example.com"
}).done(function(data){
    console.log("ajax succeeded");
}).fail(function(data){
    console.log("ajax failed");
});

 このようにすれば、共通処理を定義して、かつ個別の処理も付け加えることができるメソッドを定義できます。順に解説しましょう。

var deferred = new $.Deferred;

 deferredオブジェクトをnewしています。後述するresolveとrejectに使います。

deferred.resolve(data);
deferred.reject(data);

 deferredに対し、resolve(解決)またはreject(拒否)を通知します。対応表は以下です。

通知メソッド 実行されるメソッド
resolve done
reject fail
notify progress

 ほか、いずれの通知でも実行される、alwaysというメソッドもあります。

 なお、これらのメソッドには引数を設定することができますが、これらの引数はそのまま実行先へ渡されます。

return deferred.promise();

 promiseオブジェクトを返します。promiseオブジェクトはDeferredオブジェクトと似ていますが、resolveやrejectなどの通知メソッドを持たないため、外部から故意にdeferredオブジェクトの状態を変更されることを予防できます。

 基本的にはpromiseオブジェクトを返すと覚えておけばよいでしょう。

$.whenを使う

 非同期処理が全て完了してから行いたい処理がある場合は、$.when()を使いましょう。

$.when([
    $.ajax({url: "www1.example.com"}), 
    $.ajax({url: "www2.example.com"}), 
    $.ajax({url: "www3.example.com"}),
    .
    .
    .
    ]).done(function(data1,data2,data3, ...){
        // ajax通信がすべて完了してからここの処理が行われる
    });

Deferred利用方法まとめ

  1. new $.Deferredでdeferredオブジェクトを生成する。
  2. 非同期処理を作成し、成功時にresolve()、失敗時にreject()を呼ぶ。
  3. return deferred.promise()メソッドを呼び、promiseオブジェクトを返す。
  4. 呼び元でdone()や、fail()時の動作を定義する。
  5. 複数のDeferredオブジェクトを同期させたい場合は、$.when()メソッドを使う。

 jQueryのajaxや、fadeInなどの予めDeferred(promise)オブジェクトを返すメソッドの場合は、4番の動作定義だけで利用できますから、すごく便利ですね。

 直感的には分かりづらいDeferredオブジェクトですが、うまく使えばコールバックをコードから排除でき、可読性の高いコードを記述することができるようになります。

 まだまだ現役のjQueryですから、有効に利用して素晴らしいコードを書いていきたいものですね。