Deferred是jQuery中對CommonJS的異步模型實作,旨在提供通用的接口,簡化異步程式設計難度。
其是一個可鍊式操作的對象,提供多個回調函數的注冊,以及回調列隊的回調,并轉達任何異步操作成功或失敗的消息。
由于其對jQuery Callbacks的依賴性,如果沒有概念的朋友可以檢視jQuery Callbacks。
jQuery.Deferred( [beforeStart ] )
建立一個Deferred對象。
beforeStart:
類型: Function( Deferred deferred ) 一個在構造函數傳回前運作的處理函數。
resolve、reject、notify
Defferred中定義了三種動作,resolve(解決)、reject(拒絕)、notify(通知),對應Callbacks對象的fire動作。
進而又提供了可以定義運作時的this對象的fire,即fireWith,是以又有擴充了三個對應的操作resolveWith、rejectWith、notifyWith。
内部對應的事件分别是:done(操作完成)、fail(操作失敗)、progress(操作進行中),也就是Callbacks對象的add方法添加監聽。
舉個簡單的例子,我們可以通過deferred.done注冊上一個動作完成後的,那麼當有地方觸發了deferred.resolve或者deferred.resolveWith(這兩個方法的差别在于能不能定義回調函數的this對象)時,則回調注冊的函數。
其他對應的也是一樣的。
代碼上大概是這樣的:
var dtd = $.Deferred(); // 建立一個deferred對象
var wait = function(dtd){
var tasks = function(){
alert("執行完畢!");
dtd.resolve(); // 改變deferred對象的執行狀态
};
setTimeout(tasks,5000);
return dtd;
};
這樣我們就有了一個5000ms延遲的wait函數。于是我們就可以這麼調用:
wait(dtd).done(function(){ alert("成功了!"); })
.fail(function(){ alert("出錯啦!"); });
then
then方法提供了三種事件的注冊,隻要按順序作為參數傳進去就可以了。
then: function( /* fnDone, fnFail, fnProgress */ ) {
//分别對應完成後運作的函數,失敗後運作的函數,正在運作過程中運作的函數
var fns = arguments;
//傳回一個新的Deferred的promise,then是上一個Deferred運作後才運作的
return jQuery.Deferred(function( newDefer ) {
//分别對不同狀态注冊函數
jQuery.each( tuples, function( i, tuple ) {
var action = tuple[ 0 ], //取出動作名
fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; //取出對應回調函數
// 分别對目前的Deferred對象注冊回調函數,也就是注冊deferred[ done | fail | progress ]
deferred[ tuple[1] ](function() {
var returned = fn && fn.apply( this, arguments );
//如果傳進來的回調函數會傳回Deferred對象則在該對象上注冊事件
if ( returned && jQuery.isFunction( returned.promise ) ) {
returned.promise()
.done( newDefer.resolve )
.fail( newDefer.reject )
.progress( newDefer.notify );
//否則對建立出來的newDefer執行對應事件
} else {
//如果上一個函數有傳回值則接受傳傳回值,否則傳上一個Deferred傳來的參數
newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
}
});
});
fns = null;
}).promise();
},
Promise
Promise隻提供Deferred對象中的,
then
,
done
,
fail
,
always
.
pipe
, 和
isResolved
isRejected,防止使用者自行改變Deferred的狀态。
完整的Deferred
jQuery.Deferred = function( func ) {
var tuples = [
// 動作, 監聽事件, 回調函數列隊, 最終狀态
[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
[ "notify", "progress", jQuery.Callbacks("memory") ]
],
state = "pending",
//定義promise對象
promise = {
//傳回目前狀态
state: function() {
return state;
},
//無論成功還是失敗都運作回調函數
always: function() {
deferred.done( arguments ).fail( arguments );
return this;
},
then: function( /* fnDone, fnFail, fnProgress */ ) {
//分别對應完成後運作的函數,失敗後運作的函數,正在運作過程中運作的函數
var fns = arguments;
//傳回一個新的Deferred的promise,then是上一個Deferred運作後才運作的
return jQuery.Deferred(function( newDefer ) {
//分别對不同狀态注冊函數
jQuery.each( tuples, function( i, tuple ) {
var action = tuple[ 0 ], //取出動作名
fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; //取出對應回調函數
// 分别對目前的Deferred對象注冊回調函數,也就是注冊deferred[ done | fail | progress ]
deferred[ tuple[1] ](function() {
var returned = fn && fn.apply( this, arguments );
//如果傳進來的回調函數會傳回Deferred對象則在該對象上注冊事件
if ( returned && jQuery.isFunction( returned.promise ) ) {
returned.promise()
.done( newDefer.resolve )
.fail( newDefer.reject )
.progress( newDefer.notify );
//否則對建立出來的newDefer執行對應事件
} else {
//如果上一個函數有傳回值則接受傳傳回值,否則傳上一個Deferred傳來的參數
newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
}
});
});
fns = null;
}).promise();
},
// 如果deferred存在,将promise合并到deferred裡,否則傳回prmoise
promise: function( obj ) {
return obj != null ? jQuery.extend( obj, promise ) : promise;
}
},
deferred = {};
// 向後相容
promise.pipe = promise.then;
// 對deferred添加剩餘的方法
jQuery.each( tuples, function( i, tuple ) {
//取出對應列隊
var list = tuple[ 2 ],
//取出對應狀态
stateString = tuple[ 3 ];
// 賦予promise[ done | fail | progress ] = list.add
promise[ tuple[1] ] = list.add;
// 對狀态添加事件處理
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;
};
// 注冊有with的方法
deferred[ tuple[0] + "With" ] = list.fireWith;
});
// 将promise中的方法合并到deferred裡
promise.promise( deferred );
// 如果jQuery.Deferred中的參數存在,則先用這個參數對deferred改造
if ( func ) {
func.call( deferred, deferred );
}
// 完成
return deferred;
};
jQuery.when
jQuery.when是一個幫助Deferred隊列處理的工具,如果傳單一Deferred進去,則會傳回其promise,如果傳多個Deferred進去,則會建立一個Deferred用以管理該Deferred隊列。
- 如果隊列中有一個Deferred失敗,則整個隊列失敗。
- 如果隊列中所有Deferred成功,則整個隊列成功。
- 如果隊列中所有Deferred開始運作,則整個隊列正在運作。
jQuery.when = function( subordinate /* , ..., subordinateN */ ) {
var i = 0,
//将arguments轉成數組
resolveValues = core_slice.call( arguments ),
//傳入Deferred對象總數
length = resolveValues.length,
// 未完成的Deferred總數
remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
// Deferred隊列管理器,如果參數隻有一個Deferred則傳回該Deferred
deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
// 更新resolve和progress的Deferred數量,全部處在這兩個狀态則通知管理器
updateFunc = function( i, contexts, values ) {
return function( value ) {
contexts[ i ] = this;
values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
if( values === progressValues ) {
deferred.notifyWith( contexts, values );
} else if ( !( --remaining ) ) {
deferred.resolveWith( contexts, values );
}
};
},
progressValues, progressContexts, resolveContexts;
// 如果傳入Deferred總量大于1,則添加事件處理
if ( length > 1 ) {
progressValues = new Array( length );
progressContexts = new Array( length );
resolveContexts = new Array( length );
for ( ; i < length; i++ ) {
//判斷參數是不是可用的Deferred
if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
resolveValues[ i ].promise()
//單個成功則更新成功數
.done( updateFunc( i, resolveContexts, resolveValues ) )
//單個失敗則整個列隊失敗
.fail( deferred.reject )
//單個開始運作則更新運作中的個數
.progress( updateFunc( i, progressContexts, progressValues ) );
//不可用則未完成數減1
} else {
--remaining;
}
}
}
// 如果沒有任何可用Deferred則直接通知管理器,列隊完成
if ( !remaining ) {
deferred.resolveWith( resolveContexts, resolveValues );
}
//傳回Promise
return deferred.promise();
};
其主要通過内置一個Deferred來管理隊列的運作狀态,不過其隻将Promise暴露在外,而用閉包将所有Deferred保護起來。