深入理解jQuery中的Deferred

asdf222 8年前發布 | 25K 次閱讀 jQuery JavaScript開發

引入

1  在開發的過程中,我們經常遇到某些耗時很長的javascript操作,并且伴隨著大量的異步。

2  比如我們有一個ajax的操作,這個ajax從發出請求到接收響應需要5秒,在這5秒內我們可以運行其他代碼段,當響應到達后,我們需要判斷響應的結果(無非就是成功或者失敗),并 根據不同的結果  添加回調函數

3  為了有效的簡潔的 添加回調函數 jQuery引入了Callbacks。

4  而為了方便的 根據不同的結果( 或者根據各種跟結果有關的邏輯,比如不管是成功或者失敗) 添加回調函數,jQuery引入了Deferred。

    $.ajax("test.html")
        .done(function(){ alert("success"); })
        .fail(function(){ alert("error"); });

5  因而Deferred與Callbacks是密不可分的,事實上,Callbacks也是從Deferred中分離出去的

回顧Callbacks

1  Callbacks大體架構

2 Callbacks源碼分析:


  define([
    "./core",
    "./var/rnotwhite"
], function( jQuery, rnotwhite ) {

// String to Object options format cache var optionsCache = {};

// Convert String-formatted options into Object-formatted ones and store in cache / 如果: var a = $.Callback('once memory') 則 optionsCache中會有這么一項:"once memory":{memory:true,once:true}/ function createOptions( options ) { var object = optionsCache[ options ] = {}; jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { object[ flag ] = true; }); return object; }

/*

  • Create a callback list using the following parameters: *
  • options: an optional list of space-separated options that will change how
  • the callback list behaves or a more traditional option object *
  • By default a callback list will act like an event callback list and can be
  • "fired" multiple times. *
  • Possible options: *
  • once: will ensure the callback list can only be fired once (like a Deferred) *
  • memory: will keep track of previous values and will call any callback added
  • after the list has been fired right away with the latest "memorized"
  • values (like a Deferred) *
  • unique: will ensure a callback can only be added once (no duplicate in the list) *
  • stopOnFalse: interrupt callings when a callback returns false / jQuery.Callbacks = function( options ) {

    // Convert options from String-formatted to Object-formatted if needed // (we check in cache first) options = typeof options === "string" ? ( optionsCache[ options ] || createOptions( options ) ) : jQuery.extend( {}, options );

    var // Last fire value (for non-forgettable lists) memory, // Flag to know if list was already fired list是否已經被fire函數調用過 fired, // Flag to know if list is currently firing 當前是否正在調用fire函數 firing, // First callback to fire (used internally by add and fireWith) 第一個被執行的回調函數在list的位置 firingStart, // End of the loop when firing fire函數要運行的回調函數的個數 firingLength, // Index of currently firing callback (modified by remove if needed) 當前正在執行的回調函數的索引 firingIndex, //回調函數數組 list = [], // Stack of fire calls for repeatable lists 可重復的回調函數棧。我們可能會短時間內執行多次fire(),若當前的fire()正在迭代執行回調函數,而緊接著又執行了一次fire()時,會將下一次的fire()參數等保存至stack中,等待當前的fire()執行完成后,將stack中的fire()進行執行 stack = !options.once && [], // Fire callbacks fire = function( data ) {

      // data[0] 是一個對象,data[1]則是回調函數的參數
      memory = options.memory && data;  // 很精妙,仔細體會一下這句代碼,如果調用Calbacks時傳入了memory,則memory = data,否則memory = false
      fired = true; // 在調用本函數時,將fired狀態進行修改
      firingIndex = firingStart || 0;
      firingStart = 0;
      firingLength = list.length;
      firing = true; // 迭代回調函數之前,將firing狀態進行修改
      for ( ; list && firingIndex < firingLength; firingIndex++ ) {
          if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false &&
              options.stopOnFalse ) { // 運行回調函數的同時,檢測回調函數是否返回false,若返回false,且調用Callbacks時傳入stopOnFalse參數,則終止迭代
    
              memory = false; // To prevent further calls using add 既然終止迭代了,那么之后添加的回調函數都不應該被調用,將memory設置為false
              break;
          }
      }
      firing = false;  // 迭代回調函數完成后,將firing狀態進行修改
      if ( list ) {
          if ( stack ) {  // 沒有使用once參數
              if ( stack.length ) {
                  fire( stack.shift() );
              }
          } else if ( memory ) { // 使用了once memory參數,則在迭代完回調函數之后清空list
              list = [];
          } else { // 其他
              self.disable();
          }
      }
    

    }, // Actual Callbacks object self = {

      // 將一個新的回調函數添加至list
      add: function() {
          if ( list ) {
              // First, we save the current length  首先,我們將當前的長度進行保存
              var start = list.length;
              (function add( args ) {  // 自執行函數
                  jQuery.each( args, function( _, arg ) {
                      var type = jQuery.type( arg );
                      if ( type === "function" ) {
                          if ( !options.unique || !self.has( arg ) ) {
                              list.push( arg ); // 若參數中的元素為函數且(無unique參數或者list中沒有該函數),則將該函數添加至list末尾
                          }
                      } else if ( arg && arg.length && type !== "string" ) { //  arg的長度不為0且每項的類型不為字符串,也就是args為這種情況:[[fun1,fun2...],[fun3,fun4]](不僅限于這種情況)
                          // Inspect recursively
                          add( arg );
                      }
                  });
              })( arguments );
              // Do we need to add the callbacks to the
              // current firing batch?
              // 當Callback中的firingLength變為 動態的! 也就是:只要我們向list中添加了一個新的回調函數,即使在fire()運行過程中,改變也能立即體現出來
              if ( firing ) {
                  firingLength = list.length;
              // With memory, if we're not firing then
              // we should call right away
              } else if ( memory ) { // 如果當前沒有執行回調函數,且存在memory參數,則執行新添加的回調函數
                  firingStart = start;
                  fire( memory );
              }
          }
          return this;
      },
      // Remove a callback from the list 將一個回調函數從list中移除
      remove: function() {
          if ( list ) {
              jQuery.each( arguments, function( _, arg ) {
                  var index;
                  while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                      list.splice( index, 1 );
                      // Handle firing indexes
                      if ( firing ) {
                          if ( index <= firingLength ) {
                              firingLength--;
                          }
                          if ( index <= firingIndex ) {
                              firingIndex--;
                          }
                      }
                  }
              });
          }
          return this;
      },
      // Check if a given callback is in the list.
      // If no argument is given, return whether or not list has callbacks attached.
      has: function( fn ) {
          return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
      },
      // Remove all callbacks from the list 清空數組
      empty: function() {
          list = [];
          firingLength = 0;
          return this;
      },
      // Have the list do nothing anymore  使用了這個方法,則意味著該回調對象失效了。
      disable: function() {
          list = stack = memory = undefined;
          return this;
      },
      // Is it disabled?
      disabled: function() {
          return !list;
      },
      // Lock the list in its current state 給數組上鎖
      lock: function() {
          stack = undefined;
          if ( !memory ) {
              self.disable();
          }
          return this;
      },
      // Is it locked?
      locked: function() {
          return !stack;
      },
      // Call all callbacks with the given context and arguments
      // 使用傳入的context作為當前函數的執行上下文
      fireWith: function( context, args ) {
          if ( list && ( !fired || stack ) ) {
              args = args || [];
              args = [ context, args.slice ? args.slice() : args ];
              if ( firing ) {
                  stack.push( args ); // 如果當前正在迭代執行回調函數,則將新的fire參數推入stack中
              } else {
                  fire( args );
              }
          }
          return this;
      },
      // Call all the callbacks with the given arguments
      fire: function() {
          self.fireWith( this, arguments );
          return this;
      },
      // To know if the callbacks have already been called at least once
      // 用來確定當前callback對象是否被fire()過
      fired: function() {
          return !!fired;
      }
    

    };

    return self; };

return jQuery; });

源碼講解</code></pre>

另外要注意下面兩個參數:

once:如果創建Callbacks時加入該參數,則運行數組中的所有回調函數之后,也就是fire()之后,會清空數組。

memory:會保存上一次運行fire(args)時的參數args,每當添加一個新的回調函數到數組中,會立即使用args作為參數調用新加的函數一次

Deferred講解:

Deferred大體架構:

1  先來看一看tuple數組

var tuples = [
                // action, add listener, listener list, final state
                [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],// 解決 操作成功  Callbacks對象 最終狀態為解決
                [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], // 拒絕  操作失敗 Callbacks對象 最終狀態為拒絕
                [ "notify", "progress", jQuery.Callbacks("memory") ]  // 通知  操作進行中 Callbacks對象 最終狀態無(操作進行中的最終狀態就是操作完成,完成無非就是轉變為上面兩種 成功或者失敗)
            ]

jQuery的設計理念是這樣的:

1  deferred對象有三種執行狀態----完成 失敗 進行中。

2  每種狀態對應一個Callbacks實例

3  如果執行狀態是"完成"(resolved),deferred對象立刻調用done()方法指定的回調函數(也就是執行已完成狀態對應的Callbacks實例的fire方法);如果執行狀態是"失敗",調用fail()方法指定的回調函數;如果執行狀態是"進行中",則繼續等待,或者調用progress()方法指定的回調函數。

2  promise對象和deferred對象

一個是deferred外部接口對象,一個是內部promise對象。

promise對象是一個受限的對象, 這就是所謂的受限制的deferred對象,因為相比deferred對象, promise對象沒有resolve(With), reject(With), notify(With)這些能改變deferred對象狀態并且執行callbacklist的方法了,只能是then、done、fali等方法。

3  done  fail  progress 方法 與 resolve  reject  notify方法

在上圖中我們已經說明了前三個方法就是Callbacks中的add方法。后三個調用了Callbacks中的fireWith()方法。

所以,我們可以總結出:

1  三種狀態各對應一個Callbacks實例

2  使用done 或fail 或progress時,實際上就是往各自對應的Callbacks實例中的list數組添加回調函數

3  使用resolve 或reject 或notify時,則就是運行各自對應Callbacks實例中的list數組中的回調函數

4  then方法

then方法創建了一個新的promise對象,then就是pipe,我們可以想象是一個管道。管道就是能 ‘承上啟下’(更貼切的來說,在Deferred中的then只做了承上,僅僅是個人觀點)

var a = $.Deferred();
a.then(function(val){
    console.log(val); // 2
    return val * 2
}).then(function(val){
    console.log(val); // 4
});
a.resolve(2)

如案例所示,下一個回調對象都能取到上一個回調對象的值,這樣一直可以疊加往后傳遞。

關于then可能看了非常迷糊,不要緊,上面說的是then的高級特性,平時我們基本不怎么使用的。

平時我們大部分是使用then來代替done以及fail:

$.when($.ajax( "test.php" ))
        .then(successFunction, failureFunction );

deferred源碼分析

    define([
    "./core",
    "./var/slice",
    "./callbacks"
], function( jQuery, slice ) {

jQuery.extend({

Deferred: function( func ) {
    // 創建一個tuples數組,一個promise對象,一個deferred對象,一個state變量
    var tuples = [
            // action, add listener, listener list, final state
            [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],// 解決 操作成功  Callbacks對象 最終狀態為解決
            [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], // 拒絕  操作失敗 Callbacks對象 最終狀態為拒絕
            [ "notify", "progress", jQuery.Callbacks("memory") ]  // 通知  操作進行中 Callbacks對象 最終狀態無(操作進行中的最終狀態就是操作完成,完成無非就是轉變為上面兩種 成功或者失敗)
        ],
        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() {  // 注意,這里的deferred指的不是新Deferred對象中的deferred(也就是不是指的newDefer)
                            var returned = fn && fn.apply( this, arguments );
                            // 若returned是一個deferred對象,則為returned添加一個回調函數,這個回調函數運行后使newDefer能夠接收到returned的fire()參數
                            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 ) { // 若obj非null,則將obj與promise對象結合并返回,否則返回promise對象
                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 ], // 這是一個Callbacks 對象
            stateString = tuple[ 3 ]; // 用字符串表示的狀態

        // promise[ done | fail | progress ] = list.add
        promise[ tuple[1] ] = list.add;  // Callbacks對象的add函數

        // Handle state  處理狀態
        if ( stateString ) {
            list.add(function() {
                // state = [ resolved | rejected ]
                state = stateString;  // Callbacks對象中的回調函數列表第一項:改變狀態

            // [ reject_list | resolve_list ].disable; progress_list.lock
            },
            tuples[ i ^ 1 ][ 2 ].disable,   //  因為reject,resolve是對立的,當行為為reject,那么resolve的Callbacks就無用了,將其回調函數列表清空即可
            tuples[ 2 ][ 2 ].lock ); // 當行為為reject或者resolve時,即"結果已確定",那么就不允許再調用 "操作進行中" 的Callbacks對象的fire()了
        }

        // 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合并至deferred
    promise.promise( deferred );

    // Call given func if any  在初始化完deferred對象后,會立即運行func函數,并把deferred作為第一個參數傳入
    if ( func ) {
        func.call( deferred, deferred );
    }

    // All done!
    return deferred;
},

// Deferred helper
when: function( subordinate /* , ..., subordinateN */ ) {
    /* 大體思路:
     *    若參數(參數必需是deferred對象)只有一個,則返回這個參數的promise對象
      *   若參數有多個,則生成一個新的deferred對象,并返回deferred對象的promise對象
      *      當所有參數的狀態為完成時,使新deferred對象的狀態變為完成,
      *      若有一個參數的狀態為失敗,則使新deferred對象的狀態變為失敗
      *
      * */
    var i = 0,
        resolveValues = slice.call( arguments ),//slice是數組的slice方法,一般情況下:resolveValues就是一個由deferred組成的數組
        length = resolveValues.length,

        // the count of uncompleted subordinates 沒有運行完成的deferred對象的數量
        // 如果length長度不為1或者subordinate是一個deferred對象則,remaining=length,否則remaining為0
        remaining = length !== 1 ||
            ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,//運算符優先級: 邏輯與邏輯或 優先級高于 ? :

        // the master Deferred.
        // If resolveValues consist of only a single Deferred, just use that.
        // 如果remaining為1,則使用subordinate(這是一個deferred對象)即可,否則創建一個新的deferred對象
        deferred = remaining === 1 ? subordinate : jQuery.Deferred(),

        // Update function for both resolve and progress values
        updateFunc = function( i, contexts, values ) {
            return function( value ) {
                contexts[ i ] = this;
                values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
                if ( values === progressValues ) {
                    deferred.notifyWith( contexts, values );
                } else if ( !( --remaining ) ) {
                    deferred.resolveWith( contexts, values );
                }
            };
        },

        progressValues, progressContexts, resolveContexts;

    // Add listeners to Deferred subordinates; treat others as resolved
    if ( length > 1 ) {
        progressValues = new Array( length );
        progressContexts = new Array( length );
        resolveContexts = new Array( length );
        // 迭代resolveValues中的每一個deferred對象,為其添加不同狀態下的回調函數
        for ( ; i < length; i++ ) {
            if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
                resolveValues[ i ].promise()
                    .done( updateFunc( i, resolveContexts, resolveValues ) )
                    .fail( deferred.reject )
                    .progress( updateFunc( i, progressContexts, progressValues ) );
            } else {
                --remaining;
            }
        }
    }

    // If we're not waiting on anything, resolve the master
    if ( !remaining ) {
        deferred.resolveWith( resolveContexts, resolveValues );
    }

    return deferred.promise();
}

});

return jQuery; }); </code></pre>

 

來自:http://www.cnblogs.com/MnCu8261/p/6193353.html

 

 本文由用戶 asdf222 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!