JavaScript
jQuery
Deferred

今さら聞けないjQuery.Deferred入門

More than 1 year has passed since last update.

はじめに

  • 何となくDeferredを知っているがちゃんと使ったことのない人向けの記事です。
  • 本文中の p はPromiseオブジェクトを指します。(追記:Deferredオブジェクトを使うこともありますが、普通はPromiseに対して .then().done() をぶら下げます。)

いきなり具体例

1 が出力された1秒後に 2done! が同時に出力される。

waterfall.js
function test_deferred() {
    var p = new $.Deferred().resolve().promise();

    p = p
    .then(function() {
        var d = new $.Deferred();
        setTimeout(function() {
            console.log(1);
            d.resolve();
        }, 1000);
        return d.promise();
    });

    p = p
    .then(function() {
        var d = new $.Deferred();
        setTimeout(function() {
            console.log(2);
            d.resolve();
        }, 1000);
        return d.promise();
    });

    return p;
}

test_deferred().done(function(){console.log("done!!")});

並列的に処理を実行させる場合は $.when() を用いる。

parallel.js
// titleとcomment両方が取れるまでPUTを待つ
var title, comment;

var titleDeferred = (function() {
    var dInner = new $.Deferred();
    self.trigger('getTitle', todo, function(res) {
        title = res;
        dInner.resolve();
    });
    return dInner.promise();
})();

var commentDeferred = (function() {
    var dInner = new $.Deferred();
    self.trigger('getComment', todo, function(res) {
        comment = res;
        dInner.resolve();
    });
    return dInner.promise();
})();

$.when(titleDeferred, commentDeferred).done(function() {
    // PUT要求を送る(タイトルとコメント両方がとれたタイミングで)
    todo.save({ id: todo.get('id'), title: title, comment: comment });
});

(応用)可変数のDeferredを並列実行させたいときは func.apply(ctx, argArr) を用いる。

parallelApply.js
var firstDeferred = (function() {
    var dInner = new $.Deferred();
    setTimeout(function() {
        console.log(1);
        dInner.resolve();
    }, 1000);
    return dInner.promise();
})();

var secondDeferred = (function() {
    var dInner = new $.Deferred();
    setTimeout(function() {
        console.log(2);
        dInner.resolve();
    }, 1500);
    return dInner.promise();
})();

// 配列に並列実行させるDeferredオブジェクト群をぶちこむ。
var deferredArray = [firstDeferred, secondDeferred];

// 可変数のDeferredを並列実行させる
$.when.apply($, deferredArray).done(function() {
    console.log('done');
});

.then().done() の使い分け

Case1 : p.then(callbackA).then(callbackB)...

  • この場合は、callbackAで返す Promiseオブジェクトresolve() されるまで、次の.then()ブロックには入らない。
  • p.then(callback).then(callback).then(callback).done(callback) という流れをよく見る。
  • thenでwaterfallにチェインしたい際は 必ず callbackで Promiseオブジェクト をreturnする必要がある。(追記:Deferredオブジェクトを返しても良いが普通はPromiseオブジェクトを返す。)
  • returnしないとparallelにcallbackが実行されてしまう。正確には、ひとつ前のPromiseの状態を引き継ぐ挙動になる。

Case2 : p.done(callbackA).done(callbackB)

  • この場合は、大元の p がresolve()された際に、並列的にcallbackA, callbackBが走る。
  • (当然だがdoneでチェインする際は、callbackで Promiseオブジェクト をreturnする必要なし)

Case3 : p.then(callbackA).done(callbackB).done(callbackC)

  • この場合は、callbackAで返す Promiseオブジェクト がresolve()された『後』にcallbackB、callbackCが並列的に走る。