jQuery的Deferred對象測試筆記以及源碼分析


 官方地址見: 點擊打開鏈接
 測試代碼1:(deferred.always)
always參數可以是一個function對象也可以是一個function數組,當Deferred對象resolve或者reject那么該函數會被調用。因為deferred.always()方法返回一個Deferred對象,所以其它的Deferred的方法可以繼續調用,也包括繼續調用always方法,當Deferred已經reject或者resolve,那么回調函數按照我們添加的順序被調用,調用參數就是我們提供給reject,resolve,rejectWith,resolveWith的參數
因為$.get方法返回對象是jqXHR對象,他來自於Deferred對象,於是我們可以用deferred.always()來添加成功和失敗的回調函數!
$.get( "test.php" ).always(function() {
  alert( "$.get completed with success or error callback arguments" );
});
測試代碼2:(deferred.done)
done函數的參數可以是function也可以是function數組,當Deferred已經resolve那么就會被調用,調用的順序就是我們添加的順序,該方法也是返回Deferred對象,所以適用於Deferred的方法可以鏈式調用,包括另一個done方法。當Deferred被resolved,那么done方法被調用,調用的參數就是傳遞給resolve或者resolveWith的參數。
因為get方法返回jqXHR對象,所以我們直接可以用done方法添加成功回調函數.
$.get( "test.php" ).done(function() {
  alert( "$.get succeeded" );
});
function fn1() {
  $( "p" ).append( " 1 " );
}
function fn2() {
  $( "p" ).append( " 2 " );
}
function fn3( n ) {
  $( "p" ).append( n + " 3 " + n );
}
//創建deferred對象
var dfd = $.Deferred();
dfd.done().done( [ fn1, fn2 ], fn3, [ fn2, fn1 ] ).done(function( n ) {
    $( "p" ).append( n + " we're done." );
  });
//點擊按鈕時候Deferred調用resolve方法!打印結果[1 2 and 3 and 2 1 and we're done.]
$( "button" ).on( "click", function() {
  dfd.resolve( "and" );
});
測試代碼3:(deferred.fail)
和done方法一致

測試代碼4:(deferred.isRejected)
jQuery1.7中已經過時,用deferred.state代替,如果deferred在reject狀態返回true。也就是調用了reject或者rejectWith方法的同時fail方法已經完成

測試代碼5:(deferred.isResolved)
Query1.7中已經過時,用deferred.state代替,如果deferred在resolved狀態返回true.也就是調用了resolve或者resolveWith方法的同時done方法已經完成
deferred有三種狀態,pending,reject,resolved.如果你要將deferred狀態改為resolved,那么你就要判斷當前的狀態!

測試代碼6:(deferred.notify)

只有Deferred的構建者能夠調用該方法,你可以阻止其它的代碼修改Deferred的狀態,或者通過deferred.promise()返回一個嚴格狀態的Promise對象來記錄狀態。如果
notify被調用了,那么所有通過deferred.then,deferrred.progess添加的回調都會被調用,每一個回調函數都會傳入notify的參數,任何在resolved或者rejected
狀態以后調用的notify都會被忽略

測試代碼7:(deferred.notifyWith[context,args])
只有Deferred的構建者能夠調用該方法,你可以阻止其它的代碼修改Deferred的狀態,或者通過deferred.promise()返回一個嚴格狀態的Promise對象來記錄狀態。當notifyWith
被調用,那么所有通過deferred.then,deferrred.progess添加的回調都會被調用,每一個回調函數都會傳入notifyWith的參數,任何在resolved或者rejected
狀態以后調用的notify都會被忽略

測試代碼8:deferred.pipe( [doneFilter ] [, failFilter ] )或者deferred.pipe( [doneFilter ] [, failFilter ] [, progressFilter ] )
 jQuery 1.8以后pipe方法已經過時,用deferred.then代替。 
 //過濾resolve值
var defer = $.Deferred(),
//pipe方法
  filtered = defer.pipe(function( value ) {
    return value * 2;
  });
 //傳入參數5
defer.resolve( 5 );
filtered.done(function( value ) {
  alert( "Value is ( 2*5 = ) 10: " + value );
});
//過濾reject值
var defer = $.Deferred(),
//傳入兩個參數,第二個是failFilter
  filtered = defer.pipe( null, function( value ) {
    return value * 3;
  });
defer.reject( 6 );
//這里是filtered.fail事件,上面是done事件
filtered.fail(function( value ) {
  alert( "Value is ( 3*6 = ) 18: " + value );
});
//鏈式任務
var request = $.ajax( url, { dataType: "json" } ),
  chained = request.pipe(function( data ) {
    return $.ajax( url2, { data: { user: data.userId } } );
  });
chained.done(function( data ) {
  // data retrieved from url2 as provided by the first request
});
測試代碼9(deferred.progress( progressCallbacks [, progressCallbacks ] )產生progress事件后回調函數)
當Deferred產生notify或者notifyWith事件時候回調函數,當已經resolve或者reject那么progress不會被調用。當處於resolve或者reject狀態時候,任何新添加的progress函數會馬上執行!

測試代碼10:(deferred.promise)
該方法允許異步函數,從而不打斷現在代碼的執行。promise對象僅僅暴露了Deferred的一些方法用於添加額外事件或者判斷狀態,如then, done, fail, always, pipe, progress, state and promise
沒有提供改變狀態的方法如:(resolve, reject, notify, resolveWith, rejectWith, and notifyWith)。如果提供了參數,那么就會把這些方法封裝到參數上面,並且返回,這可以把promise的行為添加到一些已經存在的對象上面.
function asyncEvent() {
  var dfd = jQuery.Deferred();
  // Resolve after a random interval
  setTimeout(function() {
    dfd.resolve( "hurray" );
  }, Math.floor( 400 + Math.random() * 2000 ) );
 
  // Reject after a random interval
  setTimeout(function() {
    dfd.reject( "sorry" );
  }, Math.floor( 400 + Math.random() * 2000 ) );
 
  // Show a "working..." message every half-second
  setTimeout(function working() {
    if ( dfd.state() === "pending" ) {
alert("pending");
      dfd.notify( "working... " );
      setTimeout( working, 500 );
    }
  }, 1 );
  // Return the Promise so caller can't change the Deferred
  //返回一個Promise,因此調用者無法在外面改變Deferred對象的狀態!
  return dfd.promise();
} 
// Attach a done, fail, and progress handler for the asyncEvent
//為異步事件添加done,fail,progress事件!
$.when( asyncEvent() ).then(
  function( status ) {
    alert( status + ", things are going well" );
  },
  function( status ) {
    alert( status + ", you fail this time" );
  },
  function( status ) {
    $( "body" ).append( status );
  }
);
//已經存在的obj對象
var obj = {
    hello: function( name ) {
      alert( "Hello " + name );
    }
  },
  //創建Deferred對象
  defer = $.Deferred();
// 使得obj具有promise的方法
defer.promise( obj );
//改變狀態為resolved
defer.resolve( "John" );
//將obj當作Promise使用
obj.done(function( name ) {
  obj.hello( name ); //"Hello John"。將resolve的參數傳入done函數
}).hello( "Karl" ); //"Hello Karl"。該hello方法是這個promise獨有的方法
測試代碼11:(deferred.reject)
只有Deferred的構建者能夠調用該方法,你可以阻止其它的代碼修改Deferred的狀態,或者通過deferred.promise()返回一個嚴格狀態的Promise對象來記錄狀態。如果狀態變為reject,那么通過
then或者fail添加的方法都會被調用。傳入的參數就是調用reject時候傳入的參數,如果當Deferred處於reject狀態時候添加的失敗回調都會馬上執行

測試代碼12:(deferred.rejectWith)
只有Deferred的構建者能夠調用該方法,你可以阻止其它的代碼修改Deferred的狀態,或者通過deferred.promise()返回一個嚴格狀態的Promise對象來記錄狀態。如果狀態變為reject,那么通過
then或者fail添加的方法都會被調用。傳入的參數就是調用rejectWith時候傳入的參數,如果當Deferred處於reject狀態時候添加的失敗回調都會馬上執行

測試代碼13:(deferred.resolve)
只有Deferred的構建者能夠調用該方法,你可以阻止其它的代碼修改Deferred的狀態,或者通過deferred.promise()返回一個嚴格狀態的Promise對象來記錄狀態。如果狀態變為resolve,那么通過
then或者done添加的方法都會被調用。傳入的參數就是調用resolve時候傳入的參數,如果當Deferred處於reject狀態時候添加的失敗回調都會馬上執行

測試代碼14:(deferred.resolveWith)

只有Deferred的構建者能夠調用該方法,你可以阻止其它的代碼修改Deferred的狀態,或者通過deferred.promise()返回一個嚴格狀態的Promise對象來記錄狀態。如果狀態變為resolve,那么通過
then或者done添加的方法都會被調用。傳入的參數就是調用resolveWith時候傳入的參數,如果當Deferred處於reject狀態時候添加的失敗回調都會馬上執行

測試代碼15:(deferred.state)
具有三種狀態:
pending狀態,非reslove和reject
resolve狀態:resolve或者resolveWith被調用,同時done方法被調用
reject狀態:reject或者rejectWith被調用,同時fail方法被調用

測試代碼16:(deferred.then( doneCallbacks, failCallbacks [, progressCallbacks ] ))
jQuery1.8以后,then方法返回一個promise對象可以對deferred的狀態和值用函數進行過濾,代替過去的pipe方法
//過濾resolve
var filterResolve = function() {
  var defer = $.Deferred(),
    filtered = defer.then(function( value ) {
      return value * 2;
    });
  defer.resolve( 5 );
  filtered.done(function( value ) {
    $( "p" ).html( "Value is ( 2*5 = ) 10: " + value );//打印10
  });
};
//過濾reject
var defer = $.Deferred(),
  filtered = defer.then( null, function( value ) {//fail
    return value * 3;
  });
defer.reject( 6 );
filtered.fail(function( value ) {
  alert( "Value is ( 3*6 = ) 18: " + value );
});
//鏈式調用
var request = $.ajax( url, { dataType: "json" } ),
  chained = request.then(function( data ) {
    return $.ajax( url2, { data: { user: data.userId } } );
  });
 
chained.done(function( data ) {
  // data retrieved from url2 as provided by the first request
});
測試代碼17:(jQuery.Deferred( [beforeStart ] ))


測試代碼18:(.promise( [type ] [, target ] ))

var div = $( "<div>" );
div.promise().done(function( arg1 ) {
  // 馬上調用,打印"true"
  alert( this === div && arg1 === div );
});
//當所有的動畫完成以后,返回的Promise變成resolved狀態,包括那些一開始就存在於callback集合中的和那些后來添加的
$( "button" ).on( "click", function() {
  $( "p" ).append( "Started..." );
  $( "div" ).each(function( i ) {
    $( this ).fadeIn().fadeOut( 1000 * ( i + 1 ) );
  }); 
  $( "div" ).promise().done(function() {
    $( "p" ).append( " Finished! " );
  });
});
//用$.when()將返回的Pormise對象變成resolved,.promise可以用於jQuery集合
var effect = function() {
  return $( "div" ).fadeIn( 800 ).delay( 1200 ).fadeOut();
}; 
$( "button" ).on( "click", function() {
  $( "p" ).append( " Started... " );
 
  $.when( effect() ).done(function() {
    $( "p" ).append( " Finished! " );
  });
});

下面我們開始jQuery是怎么完成的,請提前閱讀Callbacks相關問題

源碼問題1:測試每一個Deferred對應的三個Callbacks作用

                             var dfd=$.Deferred();
				setInterval(function()
				{
				   alert(111);
				   dfd.resolve();
				 },1000)
				dfd.done(function(){
				 alert("成功");
				}).fail(function()
				{
				  alert("失敗");
				}).progress(function(){alert("進行中!")})
note:這個例子done中的函數只會執行一次,以后在調用resolve的時候不會觸發done方法,reject也是一樣!但是如果把上面修改為notify那么一直會交替彈出:[111,進行中]。為什么完成和未完成只能觸發一次,而進行中要連續觸發?因為成功或者失敗是一種狀態,然而進行中可以連續觸發,這對進度條等有作用!之所以能夠實現只會調用一次,因為源碼中用的是jQuery.Callbacks("once memory"), 因為once+memory的組合調用了一次就會把list=[]所以下次繼續resolve(底層調用fireWith),list已經為空了!沒有效果!
源碼問題2:(memory在這里有什么作用,復習Callbacks內容)

                          var cb=$.Callbacks("memory");
				 cb.add(function(){
				   alert(1);
				});
				cb.fire();//點擊按鈕以后會把新的函數添加入cb中間去,而memory表示不需要自己調用就能執行
				//因為add里面會自己執行!會拿着上一次的參數執行,上一次如果沒有參數那么就是undefined!
				$("input").click(function()
				{
				   cb.add(function()
				   {
				     alert(2);
				   })
				});
note:到了這里你應該有了想法了,memory如果沒有手動調用的時候會自己調用!那么在上面的例子中,如果我們已經resolve了,也就是list集合中的函數已經全部調用了,那么我們現在重新添加一個done方法會怎么樣呢? 立即執行!之所以會這樣就是memory記住了上一次的參數,不用手動調用而會立即調用新的函數!(這是為什么我總結:如果沒有手動調用,在有memory的情況的下只會用老參數調用新函數;如果手動調用的時候才會發生兩個階段,階段一就是用老參數調用新函數,階段二就是用新函數調用所有的函數)這是為什么resolve以后再次添加done方法這個方法會立即執行的原因!這和下面的例子是一樣的!
源碼問題3:

                            var dfd=$.Deferred();
				setTimeout(function()
				{
				   alert(111);
				   dfd.resolve();
				 },1000)
				dfd.done(function(){
				 alert("aaa");
				}).fail(function()
				{
				  alert("失敗");
				}).progress(function(){alert("進行中!")})
                           
				$("input").click(function()
				{
				   cb.add(function()
				   {
				     alert("bbb");
				   })
				});
//點擊的時候狀態已經完成了,也就是已經resolve了,當點擊按鈕的時候馬上彈出bbb!只要狀態已經完成,那么你再進行done操作的時候會立即執行,
//這就是memory起的作用!
源碼問題4:(簡單執行)
  function aaa()
       {
         var dfd=$.Deferred();
	 setTimeout(function()
	 {
	   dfd.resolve();
	 },1000)
       }
	var newDfd=aaa();
	newDfd.done(function(){alert("成功")}).fail(function(){alert("失敗");})
   // 很顯然會彈出"成功",因為resolve會調用底層的fireWith,fireWith就會把集合中的函數全部執行,而resolve對應的Callbacks就是done!
測試問題4:(Deferred對象的狀態很容易被修改!)
       function aaa()
        {
         var dfd=$.Deferred();
	 setTimeout(function()
	 {
	 
	   dfd.resolve();
	 },1000)
	 return dfd;
       }
	var newDfd=aaa();
	newDfd.done(function(){alert("成功")}).fail(function(){alert("失敗");})
	newDfd.reject();//上面的定時器還沒有執行,失敗已經觸發了,這時候成功不會調用!說明狀態很容易修改掉
測試問題5:(為什么源碼中的要封裝promise對象,該對象沒有resolve,resolveWith等修改狀態的方法)
    function aaa()
       {
         var dfd=$.Deferred();
	 setTimeout(function()
	 {
	 
	   dfd.resolve();
	 },1000)
	 return dfd.promise();
       }
	var newDfd=aaa();
	newDfd.done(function(){alert("成功")}).fail(function(){alert("失敗");})
	newDfd.reject();//這時候不能修改狀態,還會報錯,因為promise對象沒有reject!
測試問題6:(pipe方法的使用)
                 var dfd=$.Deferred();
		    setTimeout(function()
		    {
		      dfd.resolve("hi");
		    },1000)
		  //返回一個新的延遲對象!返回值作為延遲對象的resove的參數
		 var newDfd= dfd.pipe(function(){
		       return arguments[0]+"xxx"
		  })
                 //這個會立即執行,因為上面已經resolve了!
		  newDfd.done(function(){
		    alert(arguments[0]);
		  });
jQuery.Deferred對象源碼:

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;
				},
		//不管成功還是失敗都是會回調的!也就是把這個函數同時添加如done對應的Callbacks對象中也添加進fail對於的Callbacks對象中
				//記住這里的done,fail對應於相應的Callbacks的add方法!
				always: function() {
					deferred.done( arguments ).fail( arguments );
					return this;
				},
				//分開寫,第一個是完成的回調,第二個是失敗,第三個是進度
				then: function( /* fnDone, fnFail, fnProgress */ ) {
					var fns = arguments;
					//pipe方法和then方式是同一個方法,所以返回值是一個Defered類型,參見博客中“測試問題6”
					//jQuery.Deferred中傳入一個函數表示立即執行!
					return jQuery.Deferred(function( newDefer ) {
						jQuery.each( tuples, function( i, tuple ) {
							//假如傳入了三個函數,那么第一次i為0,獲取到第一個函數也就是成功回調函數
							var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
							// deferred[ done | fail | progress ] for forwarding actions to newDefer
							//第一次調用done函數,第二次是fail,第三次是notify函數
			//deferred[ tuple[1] ]就是deferred["done"]這就是add方法,也就是把后面的那個函數添加到done里面去執行!
							deferred[ tuple[1] ](function() {
			 //獲取第一個成功回調函數的返回值,這里的arguments就是傳遞給resolve或者reject或者notify的參數!
					//之所以arguments是resolve的參數是因為:這里直接在done的Callbacks列表中加入了這個匿名函數,
								//如果外面已經resolve了那么這里就會立即執行!
								var returned = fn && fn.apply( this, arguments );
								//如果返回值是promise的處理邏輯
								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
				//如果沒有參數那么返回Deferred對象對應的promise,如果有參數那么就是讓參數繼承promise所有的方法
				//后面用到這里讓Deferred繼承promise的所有的方法!
				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里面封裝了三個Callbacks對象,promise的done|faile|progress對應於相應對象的add方法
			//done里面的函數會被封裝到jQuery.Callbacks("once memory")這個Callbacks對象上面,
			//fail會被封裝到jQuery.Callbacks("once memory")這個Callbacks對象上面,
			//progress會被封裝到jQuery.Callbacks("memory")這個Callbacks對象上面,而且這三個Callbacks是互不影響的!
			//也就是add時候只是添加到特定的Callbacks里面!
			// promise[ done | fail | progress ] = list.add
			promise[ tuple[1] ] = list.add;
			// Handle state
			//觸發了未完成那么不能觸發完成,觸發了完成不可能觸發為完成的代碼,這里就是用來控制的!
			//第一次是resolve,所以tupes[i^1]就是reject,也就是resolve執行了不能再執行reject,即讓所有的reject全部調用disable!
			//這里要深入理解Callbacks才行,原理為:
			//(1)如果是resolve類型那么我把這個reject中的集合jQuery.Callbacks("once memory")全部調用disable
			//也就說這個集合里面的list=stack=memory=undefined!(請仔細閱讀Callbacks源碼)
			//(2)而且這個函數是放在最前面的,也就是每一次開始調用的時候他都是最前調用的,以防有人調用reject改變狀態!
			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;
			};
			//(1)Deferred對象的resolveWidth等方法都是對應於Callbacks對象的fireWith方法!
			//而且Deferred對象的resolve,reject,notify方法調用的是resolveWith,rejectWith,notifyWidth
			//也就是歸根到底,resolve方法還是調用fireWidth只是要傳進去相應的參數而已!
			//從上面的代碼可以看到,如果調用resolveWith的調用者是Deferred對象,那么調用fireWith時候傳入的第一個參數就是promise
	//如果調用resolveWidth的不是Deferred對象,那么調用fireWidth時候的第一個參數就是調用者,第二個參數是resolveWith傳入的參數!
//(2)這里也就是說對於resolveWith來說,他對應於jQuery.Callbacks("once memory")的fireWith也就說resolve調用了只會調用這里面的函數的隊列
//而這里面函數的隊列對應於就是done中間的隊列,done中間的函數隊列又對應於jQuery.Callbacks("once memory")中add的方法!
			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;
	}

總結:

(1)Deferred對象有三個方法done,fail,notify,他們對應於不同的Callbacks對象的add方法,所以我們在這三個方法里面直接添加函數!如果是done方法相當於直接在done對於的Callbacks中添加了回調函數,構建出一個list集合!那么如何觸發這個集合中的函數呢?Deferred對象專門為這個Callbacks准備了resolve,resolveWith等方法(底層調用該Callbacks的fireWith方法),該方法相當於執行通過done方法添加的Callbacks中的所有的函數!(其它函數也是同樣的思路!)

(2)回調函數在promise方法上面,但是狀態改變的方法全部在deferred對象上!Deferred只是多了修改狀態的方法!
(3)如果調用resolveWith的調用者是Deferred對象,那么調用fireWith時候傳入的第一個參數就是promise, 如果調用resolveWidth的不是Deferred對象,那么調用fireWidth時候的第一個參數就是調用者,第二個參數是resolveWith傳入的參數!

关注微信公众号

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com