前言:
什麼叫做遞延對象,生成一個遞延對象隻需調用jQuery.Deferred函數,deferred這個單詞譯為延期,推遲,即延遲的意思,那麼在jQuery中
又是如何表達延遲的呢,從遞延對象中的then方法或許能找到這種延遲的行為,本文重點解讀遞延對象中的then方法
jQuery回調、遞延對象總結篇索引:
jQuery回調、遞延對象總結(上篇)—— jQuery.Callbacks
jQuery回調、遞延對象總結(中篇) —— 神奇的then方法
jQuery回調、遞延對象總結(下篇) —— 解密jQuery.when方法
設計思路:
在遞延對象構造中,分别有三組回調對象,每一組回調對象都有與之對應的行為(action,add listener),和狀态(final state),
這些行為都歸納為遞延對象中的(觸發回調,添加函數到回調清單中等)方法
jQuery.Deferred構造源碼
Deferred構造源碼除了then函數源碼外,其他都非常簡單,這裡不做過多解讀,後面将重點讨論then方法
jQuery.extend({
Deferred: function( func ) {
var tuples = [
// action, add listener, listener list, final state
[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
[ "notify", "progress", jQuery.Callbacks("memory") ]
],
state = "pending",
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
// 可以看看上篇中lock方法的各種場景調用
}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
}
// deferred[ resolve | reject | notify ]
deferred[ tuple[0] ] = function() {
// 如果方法不被借用,那麼回調中的this對象為promise,沒有觸發回調的方法
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
// 作用于then方法
if ( func ) {
func.call( deferred, deferred );
}
// All done!
return deferred;
}
});
View Code
神奇的then方法
在實際項目應用中,一個頁面或許有多個ajax請求,你可能會這樣做:
$.ajax({ url1, ... });
$.ajax({ url2, ... });
$.ajax({ url3, ... });
...
這樣做的缺點:
1、多個ajax同時發送請求,可能會造成伺服器壓力,對于富應用頁面來說,如果請求過多,那是必然的;
2、對于頁面底部,或者說首屏不展示給使用者浏覽的部分需要發送的ajax請求,沒有必要讓它一開始加載頁面後就發送請求,這樣會造成頁面響應緩慢
jQuery遞延對象中的then方法好像天生就是為了解決以上問題而設計的,它可以按照順序依次處理多個異步請求,即第一個請求處理完後,
再處理第二個請求,以此類推,這樣既可以減輕伺服器壓力,又可以先發送首屏(從上到下)頁面部分的請求,使頁面響應更快
來看看一段非常優雅的執行個體代碼
var promiseA = $.get(urlA);
promiseA.always(doneFnA, failFnA, progressFnA);
var promiseB = promiseA.then(function(){
return $.get(urlB);
});
promiseB.always(doneFnB, failFnB, progressFnB);
或者你也可以這樣寫,但并不建議:
var promiseB = $.get(urlA).then(function(){
var state = this.state();
// 針對第一個ajax請求的處理
switch (state) {
case 'resolved' :
doneFnA();
break;
case 'rejected' :
failFnA();
break;
case 'pending' :
progressA();
break;
default:
break;
}
return $.get(urlB);
});
promiseB.always(doneFnB, failFnB, progressB);
View Code
上面代碼是如何運作的呢:
首先發送第一個ajax請求,當promiseA對象執行過resolve(或reject、notify)後,即:第一個請求成功或失敗後,将依次執行回調doneFnA
(或failFnA、progressFnA),then中的匿名函數(注意代碼的順序,之前代碼順序有誤,把promiseA.always放在了then方法執行之後,現已改過來了),
匿名函數中發送第二個ajax請求,當請求成功或失敗後,将執行對應的回調函數(doneFnB或failFnB、progressFnB)
衍生後的代碼
var promiseA = $.get(urlA);
// 這裡添加promiseA的回調
var promiseB = promiseA.then(function(){
return $.get(urlB);
});
// 這裡添加promiseB的回調
var promiseC = promiseB.then(function(){
return $.get(urlC);
});
// 這裡添加promiseC的回調
var promiseD = promiseC.then(function(){
return $.get(urlD);
});
// 這裡添加promiseD的回調
再來看看then函數中的構造源碼,通過上面的執行個體分析,相信眼前的你會恍然大悟的
promise = {
then: function( /* fnDone, fnFail, fnProgress */ ) {
var fns = arguments;
// 傳回後的promise對象與newDefer對應
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 );
// 如果回調傳回的是一個遞延對象,newDefer将根據這個傳回的遞延對象的狀态來觸發行為
if ( returned && jQuery.isFunction( returned.promise ) ) {
returned.promise()
.done( newDefer.resolve )
.fail( newDefer.reject )
.progress( newDefer.notify );
}
// 如果回調傳回的不是一個遞延對象,newDefer将根據第一個(deferred)遞延對象的狀态來觸發行為
else {
newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
}
});
});
fns = null;
}).promise();
}
}
PS: 如有描述錯誤,請幫忙指正,如果你們有不明白的地方也可以發郵件給我,
如需轉載,請附上本文位址及出處:部落格園華子yjh,謝謝!