jQueryのDeferredサンプル

jQueryのDeferredを使うと非同期処理を連結して簡潔に書けるよ
という記事を読んだけど、いまいちピンと来ないのでサンプルを書くことにしました。


今回は、Webページの初期化処理が全て終わったらボタンを使用可能にするというサンプルを、Deferredあり/なしで書き比べたいと思います。
対象はjQuery.1.9です。



使ったファイルはここに置いておきます。
jquery-deferred.zip 直

初期化の流れ

  1. リストボックス「candidates」を、サーバのファイル「candidates.txt」を元に初期化する
  2. リストボックス「languages」を、サーバのファイル「languages.txt」を元に初期化する
  3. 上記2つの処理がどちらも完了したら、ボタンを有効化する

ファイル構成

- deferred_0.html (deferred不使用)
- deferred_1.html (deferred使用)
+ txt
  - candidates.txt
  - languages.txt

ベースになるhtml


<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>
// リストボックスを初期化
function setListItems(jqObject, items)
{
    for (var i = 0; i < items.length; i++)
    {
        var item = $('<option>')
        item.val(items[i])
        item.text(items[i])
        jqObject.append(item)
    }
}
$(function(){
    // ************************************************
    // ここをDeferredあり/なしで比べる
    // ************************************************
})
</script>
</head>
<body>
   <table>
     <tr><td>vote:</td><td><select id='candidates' style='width:100px;'></select></td></tr>
     <tr><td>lang:</td><td><select id='languages' style='width:100px;'></select></td></tr>
     <tr><td><button id='submit' disabled>submit</button></td></tr>
   </table>
</body>
</html>

Deferredなしの場合

処理1.と、処理2.を、jQuery.getをネストさせれば簡単に実現できます。けれど、2つのリストボックスの初期化処理は、依存関係のある処理ではないため、並行に処理されてほしいはず。
そこで2つの処理が終了したか判断するための変数を用意し、各処理の最後に変数をインクリメント、2になったら両方の処理が終了したと判断し、ボタンを有効にします。

$(function(){
    // 終了処理
    var n = 0
    function finalize()
    {
        if (++n >= 2)
        {
            // ボタンを有効化
            $('#submit').removeAttr('disabled')
        }
    }

    // candidatesリストボックスを初期化
    $.get('./txt/candidates.txt', function(data){
        setListItems($('#candidates'), data.trim().split('\n'))
        finalize()
    })

    // languagesリストボックスを初期化
    $.get('./txt/languages.txt', function(data){
        setListItems($('#languages'), data.trim().split('\n'))
        finalize()
    })
})

Deferredありの場合

同じ処理をDeferredありで書く場合、jQuery.then()がDeferredオブジェクトを返すのを利用して、jQuery.whenで2つの処理の同期を取ります。

$(function(){
    $.when(
        // candidatesリストボックスを初期化
        $.get('./txt/candidates.txt').then(function(data){
            setListItems($('#candidates'), data.trim().split('\n'))
        }),
        // languagesリストボックスを初期化
        $.get('./txt/languages.txt').then(function(data){
            setListItems($('#languages'), data.trim().split('\n'))
        })
    ).done(function(){
        // ボタンを有効化
        $('#submit').removeAttr('disabled')
    })
})

つまり、

  • 依存関係があり、逐次に処理したい場合、処理を.then()で繋ぐ
  • 依存関係がなく、並行に処理したいが、最終的に同期させたい場合、処理を.when()で繋ぐ

ということですね。やっと理解できた。

複雑な依存関係を持つ長い処理の場合、Deferredを使った方がスマートに書けるでしょうね。