接下來我們來看下jQuery的延遲對象Deferred。
我們前面講過Callbacks函數(shù),其實Deferred就是在此基礎(chǔ)進行了擴展,Deferred是對異步函數(shù)的統(tǒng)一管理。
源碼
/**源碼2999行**/
jQuery.extend({
Deferred: function( func ) {
var tuples = [
// 類似Callbacks的fire, 類似Callbacks的add, Callbacks, 執(zhí)行完畢的狀態(tài)
[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
[ "notify", "progress", jQuery.Callbacks("memory") ]
],
state = "pending",//沒有執(zhí)行之前狀態(tài)默認為state
promise = {
state: function() {
return state;
},
always: function() {
deferred.done( arguments ).fail( arguments );
return this;
},
then: function( /* fnDone, fnFail, fnProgress */ ) {
var fns = arguments;
return jQuery.Deferred(function( newDefer ) {
jQuery.each( tuples, function( i, tuple ) {
var action = tuple[ 0 ],
fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
// deferred[ done | fail | progress ] for forwarding actions to newDefer
deferred[ tuple[1] ](function() {
var returned = fn && fn.apply( this, arguments );
if ( returned && jQuery.isFunction( returned.promise ) ) {
returned.promise()
.done( newDefer.resolve )
.fail( newDefer.reject )
.progress( newDefer.notify );
} else {
newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
}
});
});
fns = null;
}).promise();
},
// Get a promise for this deferred
// If obj is provided, the promise aspect is added to the object
promise: function( obj ) {
return obj != null ? jQuery.extend( obj, promise ) : promise;
}
},
deferred = {};
// Keep pipe for back-compat
promise.pipe = promise.then;
// Add list-specific methods
jQuery.each( tuples, function( i, tuple ) {
var list = tuple[ 2 ],
stateString = tuple[ 3 ];
// promise[ done | fail | progress ] = list.add
promise[ tuple[1] ] = list.add;
// Handle state
if ( stateString ) {
list.add(function() {
// state = [ resolved | rejected ]
state = stateString;
// [ reject_list | resolve_list ].disable; progress_list.lock
}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
}
// deferred[ resolve | reject | notify ]
deferred[ tuple[0] ] = function() {
deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
return this;
};
deferred[ tuple[0] + "With" ] = list.fireWith;
});
// Make the deferred a promise
promise.promise( deferred );
// Call given func if any
if ( func ) {
func.call( deferred, deferred );
}
// All done!
return deferred;
}
});
根據(jù)代碼我們能夠解構(gòu)出該函數(shù)主要的兩個對象promise 和 deferred
promise解構(gòu)
promise = {
state,
always,
pipe = then,
promise,
done,
fail,
progress
}
deferred解構(gòu)
deferred = {
state,
always,
pipe = then,
promise,
done,
fail,
progress,
resolve,
reject,
notify,
resolveWith,
rejectWith,
notifyWith
}
相信看到這里很多理解了Callbacks的同學(xué)已經(jīng)發(fā)現(xiàn)了,其實deferred 和 promise的區(qū)別其實就是,deferred對象提供了Callbacks的"fire"方法,而promise只有Callbacks的"add"。不過肯定很多同學(xué)肯定有跟我一樣的疑惑,為什么要這樣設(shè)計呢?其實也是比較好理解的,就拿ajax來說吧,打比方說我們ajax獲取數(shù)據(jù)是否成功的狀態(tài)能夠被我們自己改變嗎,答案是不能。所以deferred是提供給"內(nèi)部方法"使用的,如果給"外部"使用我們就返回promise對象。例如:
function getDef(){
var def = $.Deferred();
//模擬延遲操作
setTimeout(function(){
def.resolve();
}, 1000);
return def.promise();
}
var def = getDef();
def.done(function(){
console.log(1);
});
這個時候getDef()所返回的promise已經(jīng)不包含'fire'方法,所以狀態(tài)不會被隨便串改。
假設(shè)我們上面getDef函數(shù)直接返回def,是不是會存在下面這種情況
function getDef(){
var def = $.Deferred();
//模擬延遲操作
setTimeout(function(){
def.resolve();
}, 1000);
return def;
}
var def = getDef();
def.done(function(){
console.log(1);
});
def.resolve();
//發(fā)現(xiàn)程序一執(zhí)行控制臺就輸出1了。
接下來我們來具體看一下源碼是怎么實現(xiàn)的吧。
/**源碼3046行**/
var tuples = [
[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
[ "notify", "progress", jQuery.Callbacks("memory") ]
]
/**源碼3051行**/
jQuery.each( tuples, function( i, tuple ) {
var list = tuple[ 2 ],
stateString = tuple[ 3 ];
// promise[ done | fail | progress ] = list.add
/**源碼3056行**/
promise[ tuple[1] ] = list.add;
// Handle state
/**源碼3059行**/
if ( stateString ) {
list.add(function() {
// state = [ resolved | rejected ]
state = stateString;
// [ reject_list | resolve_list ].disable; progress_list.lock
}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
}
// deferred[ resolve | reject | notify ]
/**源碼3069行**/
deferred[ tuple[0] ] = function() {
deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
return this;
};
deferred[ tuple[0] + "With" ] = list.fireWith;
});
首先來看下tuples變量吧
1、resolve、reject、notify 等于 Callbacks 方法中提供的fire方法。源碼3069行進行的賦值。
2、done、fail、progress 等于Callbacks 方法中提供的add方法。源碼3056行進行的賦值。
3、源碼3059行,會發(fā)現(xiàn)源碼對 state = [ resolved | rejected ] 的list,add了方法,其實這段代碼的意思是指 當執(zhí)行玩resolve和reject,對state的狀態(tài)進行修改,如果執(zhí)行完resolve(成功)就不能再執(zhí)行reject(失?。┝恕2M行中的list進行了鎖定操作。
接下來我們來看一下稍微復(fù)雜點的then方法,首先貼上源碼
/**源碼3017行**/
then: function( /* fnDone, fnFail, fnProgress */ ) {
var fns = arguments;
return jQuery.Deferred(function( newDefer ) {
jQuery.each( tuples, function( i, tuple ) {
var action = tuple[ 0 ],
fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
// deferred[ done | fail | progress ] for forwarding actions to newDefer
/**源碼3024行**/
deferred[ tuple[1] ](function() {
var returned = fn && fn.apply( this, arguments );
if ( returned && jQuery.isFunction( returned.promise ) ) {
/**源碼3071行**/
returned.promise()
.done( newDefer.resolve )
.fail( newDefer.reject )
.progress( newDefer.notify );
} else {
/**源碼3076行**/
newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
}
});
});
fns = null;
}).promise();
}
了解源碼之前我們先來看看then有哪些寫法,首先是第一種比較簡單的寫法,可以分別傳3個回調(diào)函數(shù)分別對應(yīng)3個狀態(tài)的回調(diào)函數(shù)
function getDef(){
var def = $.Deferred();
//模擬延遲操作
setTimeout(function(){
def.resolve();
//def.reject();
//def.notify();
}, 1000);
return def;
}
var def = getDef();
def.then(function(){
console.log('成功');
}, function(){
console.log('失敗');
}, function(){
console.log('進行中');
});
該功能的實現(xiàn)代碼是在源碼的3024行實現(xiàn)的,將傳入的三個函數(shù)分別add [done | fail | progress] 到對應(yīng)的list中,最后直接通過var returned = fn && fn.apply( this, arguments );這句來執(zhí)行回調(diào)函數(shù)。
then的第二種使用方法
function getDef(){
var def = $.Deferred();
//模擬延遲操作
setTimeout(function(){
def.resolve();
// def.resolve();
// def.notify();
}, 1000);
return def;
}
var def = getDef();
def.then(function(){
console.log('成功');
return '123';
}, function(){
console.log('失敗');
}, function(){
console.log('進行中');
}).then(function(a){
console.log(a);
}, function(){
console.log('失敗1');
}, function(){
console.log('進行中1');
});
then每次執(zhí)行完畢之后都會返回一個promise(包含then方法),所以一直通過then進行鏈接。然后通過源碼3076行進行參數(shù)傳遞。如果return的是一個Deferred對象,則可以通過3071行實現(xiàn)鏈式寫法。例如:
function getDef(){
var def = $.Deferred();
//模擬延遲操作
setTimeout(function(){
def.resolve();
// def.resolve();
// def.notify();
}, 1000);
return def.promise();
}
var def = getDef();
def.then(function(){
console.log('成功');
var def1 = getDef();
return def1;
}).done(function(){
console.log('成功1');
});
程序執(zhí)行1秒之后打印成功,然后再過一秒打印出成功1