$.Deferred
コンストラクタである $.Deferred()
内では、まず Promise
が作られ、それをクローンして resolve()
, reject()
, progress()
を足した Deferred
が返される。
以下は https://github.com/jquery/jquery/blob/1.7.2/src/deferred.js から抜粋。
jQuery.extend({
Deferred: function( func ) {
var doneList = jQuery.Callbacks( "once memory" ),
failList = jQuery.Callbacks( "once memory" ),
progressList = jQuery.Callbacks( "memory" ),
state = "pending",
lists = {
resolve: doneList,
reject: failList,
notify: progressList
},
promise = {
done: doneList.add,
fail: failList.add,
progress: progressList.add,
// 中略...
},
deferred = promise.promise({}),
key;
for ( key in lists ) {
deferred[ key ] = lists[ key ].fire;
deferred[ key + "With" ] = lists[ key ].fireWith;
}
// 中略...
return deferred;
}
// 後略...
});
この Promise
と Deferred
はクロージャ内の同じ変数を操作する。つまり、インターフェイスは異なるが、持っている 状態 は同じ。ここでいう 状態 とは、基本的には resolve()
, reject()
, progress()
に対応した doneList
, failList
, progressList
という三つの $.Callbacks のこと。
$.Callbacks
ここで出てきた Callbacks
とは、jQuery 1.7 から導入された、複数のコールバックを管理してくれるオブジェクト。ざっくり言うと add()
でコールバック関数を追加し、fire()
で実行する。ちなみに Callbacks
のメソッドは this
を返すので、メソッドチェーンできる。
function echo(message) {
console.log(message);
}
$.Callbacks()
.add(echo)
.fire('hello');
// => hello と表示される。
フラグ
コンストラクタである $.Callbacks()
には、挙動を変えるためのフラグを引数として渡すことができる。複数のフラグが指定可能。$.Deferred
の実装で使われている once
と memory
だけ見てみる。
once フラグ
一回しか fire()
できない Callbacks
になる。
$.Callbacks('once')
.add(echo)
.fire('hello')
.fire('world');
// => hello しか表示されない。
memory フラグ
最後の fire()
の実行を覚えていて、その後に add()
しても、コールバックが実行される。なお、事前に複数回 fire()
されていても、コールバックは一回しか実行されない。引数は最後に実行された fire()
のものが使われる。
$.Callbacks('memory')
.fire('hello')
.fire('world')
.add(echo);
// => world とだけ表示される。
disable() と lock() の違い
disable()
と lock()
は、$.Deferred
の実装の中で使われており、Callbacks
を無効にするらしいが、ドキュメントを読んでもいまいち違いがよくわからなかった。ソースを見ると memory
フラグがある時の挙動が違う様子。disable()
するとコールバックがいっさい実行されなくなるのに対して、lock()
の後に add()
した場合はコールバックが実行される。
$.Callbacks('memory')
.fire('hello')
.disable()
.add(echo);
// => 何も表示されない。
$.Callbacks('memory')
.fire('hello')
.lock()
.add(echo);
// => hello と表示される。
$.Deferred での $.Callbacks の使われ方
resolve()
, reject()
, progress()
の主な働きは、対応する Callbacks
を fire()
すること。
Deferred は一度しか resolve()
/reject()
できないようになっている。doneList
, failList
は once
指定されているので一回しか fire()
できない。さらに resolve()
/reject()
は、他方の $.Callbacks を disable()
し、progressList
を lock()
する。
ただし、三つの Callbacks は memory フラグが指定されているので、fire()
された後も add()
されればコールバックが実行される。このため resolve()
した後でも done()
などで足したコールバック関数は即座に実行される。