Deferred是jQuery中對CommonJS的非同步模型實現,旨在提供通用的介面,簡化非同步程式設計難度。
其是一個可鏈式操作的物件,提供多個回撥函式的註冊,以及回撥列隊的回撥,並轉達任何非同步操作成功或失敗的訊息。
由於其對jQuery Callbacks的依賴性,如果沒有概念的朋友可以檢視jQuery Callbacks。
jQuery.Deferred( [beforeStart ] )
建立一個Deferred物件。
beforeStart:
一個在建構函式返回前執行的處理函式。
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保護起來。