$.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() などで足したコールバック関数は即座に実行される。