2015年12月12日 星期六

jQuery之Deferred的原理(參看源碼)

了解了jQuery的Callbacks以後,我們可以藉由觀看jQuery的源始碼(jquery-1.11.3.js)來較清楚地了解Deferred到底做了什麼事及怎麼實現的,以更好的運用Deferred及其相關API。

首先,我們先把Deferred的jQuery源碼貼出來:

 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 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[ tuple[ 0 ] + "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;
 }

再來一步步解析:
首先我們可以看到:
deferred = {};

return deferred;
得知在$.Callbacks()會想辨法建立好Deferred物件們並傳回。

再來我們可以看到其先準備了幾個變數:

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"
可以看到一開始的 state被設為pending,二維陣列的tuples的前兩個一維陣列的"resolve"及"reject"剛好對應了Deferred的resolve()及reject()的函式名稱,而"done"、"fail"也對應到了Deferred的done()及fail()的函式名稱,很顯然tuples[0]跟異步函式完成有關、tuples[1]跟沒成功完成有關。

因為tuples[0]跟tuples[1]在後續的處理一樣,可以以此類推,所以我們就先只討論tuples[0]的處理,可以看到:

  1. tuples[0][0] = "resolve" :對應 Deferred 的 resolve() 的函式名稱;。
  2. tuples[0][1] = "done" :對應 Deferred 的 done() 的函式名稱;。
  3. tuples[0][2] = jQuery.Callbacks("once memory") :為一個專門處理異步函式成功執行相關的Callbacks 列隊。
  4. tuples[0][3] = "resolved":對應Deferred被 resolve() 後的狀態。
接著開始為要回傳的deferred物件添加resolve()、done(),還有resolveWith()等方法:

  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;
  });

在上面的程式碼中,對tuples進行了遍歷,i為index、tuple為tuples[i],首先先用list來得到執行成功相關的Callbacks列隊,再用stateString取得tuple[3],接著對promise物件添加了一個done屬性,指向了Callbacks的add(),promise在Deferred()裡有宣告,是一個無法改變state的物件:
promise[ tuple[1] ] = list.add;
接著如果有stateString的話(即tuples[0]及tuples[1]),對list放進三個函式:
  1. 改變state狀態,例如異步執行成功相關就是要改成"resolved"
  2. function() {
         // state = [ resolved | rejected ]
         state = stateString;
    
        // [ reject_list | resolve_list ].disable; progress_list.lock
        }
  3. 如果是在處理tuples[0]的話,將tuples[1][2]指向的Callbacks列隊給disable掉;
    如果是在處理tuples[1]的話,將tuples[0][2]指向的Callbacks列隊給disable掉;
    其中 ^ 是 XOR 運算子:
    tuples[ i ^ 1 ][ 2 ].disable
  4. 將tuples[2][2]指向的Callabcks列隊給lock住:
    tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock
接著先看這一行,
deferred[ tuple[0] + "With" ] = list.fireWith;
它為deferred添加了一個屬性,resolveWith,指向了list的fireWith方法。
再來回去看上面那行程式碼:
deferred[ tuple[0] ] = function() {
    deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
    return this;
   };
它為deferred添加了一個屬性,resolve,指向的function內容其實就是去執行deferred["solvedWith"],也就是list的fireWith。

然後,利用 promise 內設定的同名函式 (函式名也叫 promise),將 promise 所擁有的所有屬性及函式都 extend 給deferred,所以 deferred 擁有所有的函式及屬性(把括了resolve().done()、then()等等,這裡要注意的是,原本 deferred 有的 promise 這個非函式屬性,在這裡會被名為 promise 的同名函式蓋掉,也就是 deferred 只能用 promise() 來存取原本的 promise 物件了),而promise只有部份(done()、then()等等)方法
promise.promise( deferred );
最後如果$.Deferred()有參入函式參數(func)的話,執行它,並且給func一個deferred參數供其利用:
  // Call given func if any
  if ( func ) {
   func.call( deferred, deferred );
  }

====================================================================
接著,我們再來看一下then()這個方法的實作內容:

    then: function( /* fnDone, fnFail, fnProgress */ ) {
     var fns = arguments;
     return jQuery.Deferred(function( newDefer ) {
      jQuery.each( tuples, function( i, tuple ) {
       var 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[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
        }
       });
      });
      fns = null;
     }).promise();
    }
首先,先用fns得到送入的函式參數,並且返回了一個新的 Deferred (也就是 newDefer ),並且執行送入 jQuery.Deferred() 裡作為輸入參數的函式,函式的內容為:
遍歷 tuples 二維陣列,以下只討論 i = 0 時的情況,其他以此類推:
先用 fn 得到 then() 的函式輸入,並且執行 deferred[tuple[1]](),也就是呼叫 newDefer 的 done(),其作用是把一個 function 放進 resolve 所管的列隊裡,也就是 tuples[0][2] ,而這個 function 裡面要做的事就是 newDefer 被呼叫 resolve() 後要做的事情,那要做什麼事情呢?

首先先執行送進 then() 中的函式,也就是 fn,並把呼叫 then 的 Deferred (也就是 deferred,不是 newDefer) 執行 resolve(arguments) 時送入的 arguments 送給 fn,並用 returned 得到 fn 回傳的 Deferred (所以在 fn 中可以宣告一個新的 Deferred 並傳回)。
var returned = fn && fn.apply( this, arguments );
在以下的程式碼中,呼叫returned的done(), fail()等函式,並將newDefer的resolve及reject函式給其當做參數:
if ( returned && jQuery.isFunction( returned.promise ) ) {
         returned.promise()
          .done( newDefer.resolve )
          .fail( newDefer.reject )
          .progress( newDefer.notify );
        } else {
         newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
所以在呼叫then()的Deferred做完異步函式時,就會執行給then()當參數的函式,而當參數的異步函式執行完後(別忘了在函式內宣告一個新的Deferred,並在執行完後將其resolved)就會執行呼叫then()時回傳的Deferred的resolve()。

如此一來,就可以用像
$.Deferred(function(defer1){
 //do something
 defer1.resolve('passing argument'); 
}).then(function(argument){
 var defer2 = $.Deferred();
 //do something
 defer2.resolve((argument));
 return defer2;
}).then(function((argument)){
 var defer3 = $.Deferred();
 //do something
 defer3.resolve((argument));
 return defer3;
}).done(function(argument){
 //do something
});

這樣的程式碼串接要順序執行的異步函式,而argument可以當參數丟進resolve()中一個一個傳進then()或done()的輸入函式當輸入參數使用。

總結:
其實Deferred的done()、fail就對應到了Callbacks的add()。
而Deferred的resolve()、reject就葑應到了Callbacks的fireWith()。
其中Deferred用了2個Callbacks來管理異步函式執行成功及失敗的行為。

沒有留言 :

張貼留言