`

Deferred源码

 
阅读更多

一、由 function(args,callback){} 到 Deferred.resolve().done(fn)

 

主要问题:添加多个回调函数需要在function函数体内进行改写,针对resovle函数执行成功、reject函数执行失败、progress函数执行过程中三种状态分别注册回调函数较为麻烦,以及对回调函数的处理机制没有通用性,可移植性差。

 

功能需要:降解回调函数注册和执行的复杂度,针对失败、成功、执行中三种状态能批量添加回调函数,处理回调函数的通用功能抽象为独立的模块。

 

设计思路:利用Event模块事件触发、绑定事件的理念,将原回调函数执行过程callback()改写为Event.emit(eventName),而具体的回调函数通过Event.addListener(eventName,fns)注册加载,这样既能实现根据执行状态的不同,分别调用特定的回调函数,同时也能解决批量注册的问题。

 

具体实现

1.Callbacks对象的fire、fireWith方法同Event模块的emit方法,而add方法等同Event模块的addListener方法。针对resolve、reject、progress三种状态分别注册回调函数队列问题,可以通过构建三个Callbacks对象解决。

 

2.Deferred对象的tuples变量以数组形式存储resovle、reject、progress三种状态的fire方法名、add方法名、Callbacks对象、状态值(resovled|rejected),使三个Callbacks对象发生作用。

var tuples = [
	[ "notify", "progress", jQuery.Callbacks( "memory" ), 
                     jQuery.Callbacks( "memory" ), 2 ],
        [ "resolve", "done", jQuery.Callbacks( "once memory" ),
                     jQuery.Callbacks( "once memory" ), 0, "resolved" ],
	[ "reject", "fail", jQuery.Callbacks( "once memory" ),
		     jQuery.Callbacks( "once memory" ), 1, "rejected" ]
]

 

 3.回调函数队列Callbacks的add方法,通过deferred[ tuples[i][1] ]=tuples[i][2].add挂载给Deferred对象;同时函数队列Callbacks的fire|fireWith方法,通过promise[ tuples[i][0] | tuples[i][0]+"With" ]=tuples[i][2].fire|fireWith挂载给promise对象。

deferred.progress注册执行过程中回调函数队列,状态为pending,promise.notify方法为触发函数。

deferred.done注册执行完成时回调函数队列,状态为resovled,promise.resovle方法为触发函数。

deferred.fail注册执行失败时回调函数队列,状态为rejected,promise.reject方法为触发函数。

当deferred.resolve触发时,执行状态改为resolved,也即执行成功,使rejected状态下触发的回调函数队列失效,并且锁死pending状态下触发的回调函数队列。deferred.reject触发时类同。

jQuery.each( tuples, function( i, tuple ) {
	var list = tuple[ 2 ],
	stateString = tuple[ 5 ];

	promise[ tuple[ 1 ] ] = list.add;

	if ( stateString ) {
		list.add(
			function() {
				state = stateString;
			},
			tuples[ 3 - i ][ 2 ].disable,
			tuples[ 0 ][ 2 ].lock
		);
	}

	list.add( tuple[ 3 ].fire );

	deferred[ tuple[ 0 ] ] = function() {
		deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
		return this;
	};

	deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
} );

 

4.与此同时,在deferred对象的基础上构造promise对象,为的是改变函数执行状态的fire方法不能在函数外部被调用,函数外部只能使用add方法添加回调函数,以使外部的一切处理不致影响运行逻辑。

var dtd=$.Deferred();

function wait(dtd){
       setTimeout(function(){
             dtd.resovle();
       },3000)

       // 返回promise对象,函数外不能改变Deferred对象状态
       return dtd.promise() 
}

wait(dtd).done(function(){
       console.log("I'm back!")
})

 

 

 

二、由Deferred.resolve().done(fn)到Deferred.pipe(fnDone,fnFail,fnProgress)

 

主要问题:添加三种状态的回调函数时需要分别使用progress|done|fail方法;只能处理单个阻塞函数,不能处理阻塞函数的递延,即阻塞函数A执行完成后,再执行阻塞函数B;回调函数队列使用参数由触发函数resovle、reject、notify提供,不能进行过滤处理,即由A回调处理后再传给B回调。

 

功能需要:实现pending、resovled、rejected状态回调函数队列的一次性注册;多个阻塞函数的递延执行;回调函数的参数在调用过程实现动态改变,并传给下一个回调函数。

 

设计思路和具体实现

1.三种状态的回调函数fnDone、fnFail、fnProgress分别加载在原deferred对象的done、fail、progress函数队列。

 

2.链式调用的考虑:递归创建新的deferred对象newDefer,返回newDefer.promise(),newDefer的触发函数resovle|reject|notify添加到原deferred对象对应回调函数队列中,以使newDefer后续注册的done、progress、fire函数队列能触发执行,即利用事件机制将newDefer函数队列的启动器emit方法加入原deferred对象的回调队列中。

 

3.若fnDone、fnFail、fnProgress返回值是普通函数,将fnDone、fnFail、fnProgress添加到原deferred对象对应回调函数队列之外,再将newDefer的触发函数resovle|reject|notify添加到该队列中,即可保证原deferred对象的状态能相应触发fnDone、fnFail、fnProgress函数得到执行,以及newDefer对象注册的函数队列同样得到执行。

同时fnDone、fnFail、fnProgress函数将def.resovle|reject|notify(args)所传参数过滤处理以后,传给通过newDefer注册的回调函数队列。

 

4.回调函数fnDone、fnFail、fnProgress的返回值是deferred对象def:在此种情况下,回调函数fnDone、fnFail、fnProgress内部预先设置了def状态改变的触发时机,即内部调用def.resovle|notify|reject,通过newDefer.promise()对象后续注册的函数队列执行时机是在def状态改变的时候,也即需要添加到def对象相应的回调函数队列中。

 

5.图解


 

 6.源码

pipe: function( /* fnDone, fnFail, fnProgress */ ) {
	var fns = arguments;

	return jQuery.Deferred( function( newDefer ) {
		jQuery.each( tuples, function( i, tuple ) {

			// Map tuples (progress, done, fail) to arguments (done, fail, progress)
			var fn = jQuery.isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];

			// deferred.progress(function() { bind to newDefer or newDefer.notify })
			// deferred.done(function() { bind to newDefer or newDefer.resolve })
			// deferred.fail(function() { bind to newDefer or newDefer.reject })
			deferred[ tuple[ 1 ] ]( function() {
				var returned = fn && fn.apply( this, arguments );
				if ( returned && jQuery.isFunction( returned.promise ) ) {
					returned.promise()
						.progress( newDefer.notify )
						.done( newDefer.resolve )
						.fail( newDefer.reject );
				} else {
					newDefer[ tuple[ 0 ] + "With" ](
						this,
						fn ? [ returned ] : arguments
					);
				}
			} );
		} );
		fns = null;
	} ).promise();
}

 

7.多个阻塞函数的问题:通过构建新的deferred对象newDefer,不仅解决了链式调用的问题,与此同时,通过newDefer注册的回调函数队列在延迟对象def内部函数def.resovle改变状态值时触发执行,因此能满足一个阻塞函数执行完毕后再调用另一个阻塞函数的需求。

var dtd=$.Deferred();
dtd.resovle()
.pipe(function(){
       return $.ajax("getJson.json")
}).pipe(function(){
       return $.ajax("getJson.json")
}).pipe(function(){
       console.log("All ajax methods are over.")
})

 

8.参数过滤问题(针对fnDone、fnFail、fnProgress的返回值是普通函数的情况):fnDone、fnFail、fnProgress在原deferred对象的回调函数队列中执行,执行完毕后调用延迟对象newDefer的resolveWith、rejectWith、notifyWith方法,触发newDefer的回调函数队列执行,两者在同一函数中,因此fnDone、fnFail、fnProgress的返回值可以作为参数传入newDefer的resolveWith、rejectWith、notifyWith方法,再由newDefer的回调函数队列获得。

var dtd=$.Deferred();
dtd.resovle()
.pipe(function(){
       return 5
}).done(function(x){
      console.log(x*2) // 10 
})

 

 

 

三、由Deferred.pipe(fnDone,fnFail,fnProgress) 到 Deferred.then(onFulfilled,onRejected,onProgress)

 

主要问题:延迟函数若有如IO获取数据失败等错误发生时,将直接报错,后续函数没法得到执行。

 

功能需求:捕获错误并作处理。

 

设计思路:reject状态的预执行函数调用throw函数抛出错误,接着调用try-catch函数捕获该错误,完成错误捕捉以后,再执行rejectWith方法,以使通过fail方法注册的失败装填函数队列得到执行。

 

具体实现

1.通过Deferred.then方法注册的回调函数添加到另外三个Callbacks对象tuples[i][3]中,原Deferred对象通过done、fail方法预先注册的回调函数在tuples[i][2]中,两者不相干扰。

问题Deferred对象对外没有提供访问、处理相关Callbacks对象tuples[i][2]、tuples[i][3]的接口,使得Deferred对象类同Dom元素的事件机制,对外只提供注册事件和触发事件的方法,而不能对注册的事件进行删除、改写处理,这是我的学识浅薄,还是原作者尚未考虑。

then: function( onFulfilled, onRejected, onProgress ) {
	var maxDepth = 0;
	
        // do something

	return jQuery.Deferred( function( newDefer ) {

		// progress_handlers.add( ... )
		tuples[ 0 ][ 3 ].add(
			resolve(
				0,
				newDefer,
				jQuery.isFunction( onProgress ) ?
					onProgress :
					Identity,
				newDefer.notifyWith
			)
		);

		// fulfilled_handlers.add( ... )
		tuples[ 1 ][ 3 ].add(
			resolve(
				0,
				newDefer,
				jQuery.isFunction( onFulfilled ) ?
					onFulfilled :
					Identity
			)
		);

		// rejected_handlers.add( ... )
		tuples[ 2 ][ 3 ].add(
			resolve(
				0,
				newDefer,
				jQuery.isFunction( onRejected ) ?
					onRejected :
					Thrower
			)
		);
	} ).promise();
},

 

2.构建resovle函数,区分progress、reject、resovle三种状态,以及参数项返回值是否为deferred延迟对象两种情况。

 

2-1.参数项函数的返回值为deferred延迟对象,逻辑上的需要便是原Deferred对象延迟执行完成后,通过Deferred.then方法的返回值newDefer.done|fail|progress方法注册的函数队列得到执行,实现方案只要将启动函数newDefer.notifyWith|resovleWith|rejectWith加入相应的tuples[i][3]队列。


 

2-2.参数项函数的返回值为deferred延迟对象,逻辑上的需要是在该deferred延迟对象执行完成后,也即deferred.resovle|reject|notify方法执行时,使通过Deferred.then的返回值newDefer.done|fail|progress方法注册的回调函数队列得到执行,即把newDefer.resovleWith|rejectWith|notifyWith添加到deferred延迟对象的tuples[i][3]回调函数队列。Deferred.then方法中奇异地使用三次嵌套resovle函数实现,这让人匪夷所思,其中的疑问包括,调用deferred.then方法创建的anotherDefer对象,使用者并不能调用该对象的done|fail|progress方法注册函数,因此存在多余的感觉。


 

图解


 

源码

then: function( onFulfilled, onRejected, onProgress ) {
	var maxDepth = 0;
	function resolve( depth, deferred, handler, special ) {
		return function() {
			var that = this,
				args = arguments,
				mightThrow = function() {
					var returned, then;

					// Support: Promises/A+ section 2.3.3.3.3
					// https://promisesaplus.com/#point-59
					// Ignore double-resolution attempts
					if ( depth < maxDepth ) {
						return;
					}

					returned = handler.apply( that, args );

					// Support: Promises/A+ section 2.3.1
					// https://promisesaplus.com/#point-48
					if ( returned === deferred.promise() ) {
						throw new TypeError( "Thenable self-resolution" );
					}

					// Support: Promises/A+ sections 2.3.3.1, 3.5
					// https://promisesaplus.com/#point-54
					// https://promisesaplus.com/#point-75
					// Retrieve `then` only once
					then = returned &&

						// Support: Promises/A+ section 2.3.4
						// https://promisesaplus.com/#point-64
						// Only check objects and functions for thenability
						( typeof returned === "object" ||
							typeof returned === "function" ) &&
						returned.then;

					// Handle a returned thenable
					if ( jQuery.isFunction( then ) ) {

						// Special processors (notify) just wait for resolution
						if ( special ) {
							then.call(
								returned,
								resolve( maxDepth, deferred, Identity, special ),
								resolve( maxDepth, deferred, Thrower, special )
							);

						// Normal processors (resolve) also hook into progress
						} else {

							// ...and disregard older resolution values
							maxDepth++;

							then.call(
								returned,
								resolve( maxDepth, deferred, Identity, special ),
								resolve( maxDepth, deferred, Thrower, special ),
								resolve( maxDepth, deferred, Identity,
									deferred.notifyWith )
							);
						}

					// Handle all other returned values
					} else {

						// Only substitute handlers pass on context
						// and multiple values (non-spec behavior)
						if ( handler !== Identity ) {
							that = undefined;
							args = [ returned ];
						}

						// Process the value(s)
						// Default process is resolve
						( special || deferred.resolveWith )( that, args );
					}
				},

				// Only normal processors (resolve) catch and reject exceptions
				process = special ?
					mightThrow :
					function() {
						try {
							mightThrow();
						} catch ( e ) {

							if ( jQuery.Deferred.exceptionHook ) {
								jQuery.Deferred.exceptionHook( e,
									process.stackTrace );
							}

							// Support: Promises/A+ section 2.3.3.3.4.1
							// https://promisesaplus.com/#point-61
							// Ignore post-resolution exceptions
							if ( depth + 1 >= maxDepth ) {

								// Only substitute handlers pass on context
								// and multiple values (non-spec behavior)
								if ( handler !== Thrower ) {
									that = undefined;
									args = [ e ];
								}

								deferred.rejectWith( that, args );
							}
						}
					};

			// Support: Promises/A+ section 2.3.3.3.1
			// https://promisesaplus.com/#point-57
			// Re-resolve promises immediately to dodge false rejection from
			// subsequent errors
			if ( depth ) {
				process();
			} else {

				// Call an optional hook to record the stack, in case of exception
				// since it's otherwise lost when execution goes async
				if ( jQuery.Deferred.getStackHook ) {
					process.stackTrace = jQuery.Deferred.getStackHook();
				}
				window.setTimeout( process );
			}
		};
	}

	return jQuery.Deferred( function( newDefer ) {

		// progress_handlers.add( ... )
		tuples[ 0 ][ 3 ].add(
			resolve(
				0,
				newDefer,
				jQuery.isFunction( onProgress ) ?
					onProgress :
					Identity,
				newDefer.notifyWith
			)
		);

		// fulfilled_handlers.add( ... )
		tuples[ 1 ][ 3 ].add(
			resolve(
				0,
				newDefer,
				jQuery.isFunction( onFulfilled ) ?
					onFulfilled :
					Identity
			)
		);

		// rejected_handlers.add( ... )
		tuples[ 2 ][ 3 ].add(
			resolve(
				0,
				newDefer,
				jQuery.isFunction( onRejected ) ?
					onRejected :
					Thrower
			)
		);
	} ).promise();
}

 

 

 

四、由Deferred.resolve().done(fn)到$.when(deferred).done(fn)

 

主要问题Deferred.resolve().done(fn)只能根据一个延迟对象执行完毕后再调用回调函数fn,当需要根据多个延迟对象最末一个执行完成后调用回调函数fn时,Deferred.resolve().done(fn)就鞭长莫及了,$.when(deferred).done(fn)用于解决这一问题。

 

功能需求多个延迟对象最末一个执行完毕后调用回调函数。

 

设计思路:使用闭包变量remaining记录延迟对象的个数,当其中一个执行完成后,remaining-1,因此最末一个执行完成后,remaining为0,此时便可触发后续注册的回调函数得到执行。

 

具体实现:创建一个新的Deferred对象master,且master.promise作为$.when的返回值,因此在$.when方法内部,可使用master.resovleWith启动以master.done注册的回调函数得到执行,其中master.resovleWith方法中this关键字指向最后一个执行完毕的deferred对象,参数也由该deferred对象的resovle方法提供。

 

源码

function adoptValue( value, resolve, reject ) {
	var method;

	try {

		// Check for promise aspect first to privilege synchronous behavior
		if ( value && jQuery.isFunction( ( method = value.promise ) ) ) {
			method.call( value ).done( resolve ).fail( reject );

		// Other thenables
		} else if ( value && jQuery.isFunction( ( method = value.then ) ) ) {
			method.call( value, resolve, reject );

		// Other non-thenables
		} else {

			// Support: Android 4.0 only
			// Strict mode functions invoked without .call/.apply get global-object context
			resolve.call( undefined, value );
		}

	// For Promises/A+, convert exceptions into rejections
	// Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in
	// Deferred#then to conditionally suppress rejection.
	} catch ( value ) {

		// Support: Android 4.0 only
		// Strict mode functions invoked without .call/.apply get global-object context
		reject.call( undefined, value );
	}
}

jQuery.extend( {
        when: function( singleValue ) {
		var

			// count of uncompleted subordinates
			remaining = arguments.length,

			// count of unprocessed arguments
			i = remaining,

			// subordinate fulfillment data
			resolveContexts = Array( i ),
			resolveValues = slice.call( arguments ),

			// the master Deferred
			master = jQuery.Deferred(),

			// subordinate callback factory
			updateFunc = function( i ) {
				return function( value ) {
					resolveContexts[ i ] = this;
					resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
					if ( !( --remaining ) ) {
						master.resolveWith( resolveContexts, resolveValues );
					}
				};
			};

		// Single- and empty arguments are adopted like Promise.resolve
		if ( remaining <= 1 ) {
			adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject );

			// Use .then() to unwrap secondary thenables (cf. gh-3000)
			if ( master.state() === "pending" ||
				jQuery.isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {

				return master.then();
			}
		}

		// Multiple arguments are aggregated like Promise.all array elements
		while ( i-- ) {
			adoptValue( resolveValues[ i ], updateFunc( i ), master.reject );
		}

		return master.promise();
	}
} );

 

使用:

$.when(
      $.ajax("getJson1.json"),
      $.ajax("getJson2.json")// 假使延迟最久
).done(
      function(res){
            console.log(res)// 由getJson2.json的返回值构成
      }
)

 

 

 

五、整体源码解读(初稿)

define([
	"./core",
	"./var/slice",
	"./callbacks"// 回调函数的队列形式调用实现
], function(jQuery,slice){

function Identity(v){
	return v;
}
function Thrower(ex){
	throw ex;
}

jQuery.extend({

Deferred:function(func){
	var tuples=[

			// action, add listener, callbacks,
			// ... .then handlers, argument index, [final state]
			["notify","progress",jQuery.Callbacks("memory"),jQuery.Callbacks("memory"),2],
			["resolve","done",jQuery.Callbacks("once memory"),jQuery.Callbacks("once memory"),0, "resolved"],
			["reject","fail",jQuery.Callbacks("once memory"),jQuery.Callbacks("once memory"),1, "rejected"]
		],
		state="pending",
		promise={
			state:function(){
				return state;// "pending"、"resolved"、"rejected"三种状态,返回状态值
			},
			always:function(){// 成功或失败时均调用arguments函数
				deferred.done(arguments).fail(arguments);
				return this;
			},
			"catch":function(fn){
				return promise.then(null,fn);
			},

			// Keep pipe for back-compat
			// 若参数为deferred对象:
			//     注册一个新的deferred对象,起始deferred对象调用notify、resolve、reject方法时触发新deferred对象的同类方法
			//     进而执行新deferred对象中progress、done、fail方法注册的函数
			//     示例:
			//     		var dtd=$.Deferred(),deferred=$.Deferred();
			//     		function wait(dad){
			//     			setTimeout(function(){
			//     				dtd.resovle();
			//     			},3000);
			//     			
			//     			return dtd.promise();
			//     		}
			//     		wait(dtd).pipe($.Deferred())// 获得$.Deferred()返回对象
			//     		.done(fn);
			//     		实现功能似,$.Deferred()返回对象done方法中添加fn函数,dtd的done方法中也添加fn函数
			//     		dtd.resovle方法控制着dtd、$.Deferred()返回对象的done方法执行
			// 若参数为普通函数:
			// 		以deferred.resovle方法传入的参数作为函数的参数,直接执行该函数,函数的返回值作为done、fail方法的参数
			// 		或者将deferred.resovle方法传入的参数作为done、fail方法的参数
			// 		示例:
			// 			var dtd=$.Deferred();
			//     		function wait(dad){
			//     			setTimeout(function(){
			//     				dtd.resovle(a,b,c);
			//     			},3000);
			//     			
			//     			return dtd.promise();
			//     		}
			// 			wait(dtd).pipe(fn1)
			// 			.done(fn2)
			// 			fn1用来格式化dtd.resovle(a,b,c)中a、b、c参数,作为参数传给fn2
			// 			若fn1不存在,直接传递a、b、c给fn2
			// 
			// done,fail,progress状态执行函数顺序排列,tuples数组元素第四项为对应函数在参数arguments中的位置
			pipe:function(/* fnDone, fnFail, fnProgress */){
				var fns=arguments;

				return jQuery.Deferred( function(newDefer){// newDefer即使用jQuery.Deferred新创建的deferred对象
					jQuery.each(tuples, function(i,tuple){

						// fns[tuple[4]]获取pipe方法中done,fail,progress状态执行函数,即pipe方法的参数
						var fn=jQuery.isFunction(fns[tuple[4]]) && fns[tuple[4]];

						// 执行deferred.progress|done|fail方法
						deferred[tuple[1]](function(){
							var returned=fn && fn.apply(this,arguments);
							if ( returned && jQuery.isFunction(returned.promise) ){
								returned.promise()
									// 对于pipe方法返回值done|fail注册的函数,挂载启动函数
									.progress(newDefer.notify)
									.done(newDefer.resolve)
									.fail(newDefer.reject);
							}else{
								// 对于pipe方法返回值done|fail注册的函数,挂载启动函数
								newDefer[tuple[0]+"With"](
									this===promise ? newDefer.promise() : this,
									fn ? [returned] : arguments
								);
							}
						});
					});

					fns=null;

				// 返回newDefer的promise对象,使其不能使用deferred对象的resolve、reject方法在外部改变状态
				// 链式使用done|fail方法注册函数时,启动函数在pipe方法执行中已挂载
				}).promise();
			},
			then:function(onFulfilled,onRejected,onProgress){
				var maxDepth=0;
				function resolve(depth, deferred, handler, special){
					// 返回函数注册给eferred对象的tuples[i][3]
					// 通过deferred.notify|resolve|reject方法启动执行,因此是在延迟函数执行过程或完毕以后调用
					return function(){
						var that= this===promise ? undefined : this,
							// arguments由tuples[i][2]中fire方法传入
							// 即deferred对象的progress、done、fail方法传入
							args= arguments,
							mightThrow=function(){
								var returned,then;

								// Support: Promises/A+ section 2.3.3.3.3
								// https://promisesaplus.com/#point-59
								// Ignore double-resolution attempts
								if ( depth<maxDepth ){
									return;
								}

								// 执行onFulfilled、onRejected、onProgress函数,在三项有且为函数的前提下
								// 否则执行Identity函数返回args,或执行Thrower方法报错(针对rejected状态)
								returned=handler.apply(that,args);

								// Support: Promises/A+ section 2.3.1
								// https://promisesaplus.com/#point-48
								if ( returned===deferred.promise() ){
									throw new TypeError("Thenable self-resolution");
								}

								// promise.then方法所传参数deferred对象或其构造函数
								then=returned && 
									( typeof returned==="object" || typeof returned==="function" ) &&
									returned.then;

								// Handle a returned thenable
								if ( jQuery.isFunction(then) ){

									// Special processors (notify) just wait for resolution
									if ( special ){
										// promise.then方法所传参数deferred对象或其构造函数时
										// returned指向该deferred对象
										// 原deferred对象tuples[i][3]注册函数队列
										// 
										// 递归调用then方法再次创建deferred对象
										// 该deferred对象then方法在原deferred.done函数队列中添加
										// 即原deferred函数队列执行完毕后触发参数deferred对象的函数队列注册
										// 再次创建deferred对象函数队列中添加参数deferred对象的启动函数
										then.call(
											returned,
											// then方法创建的anotherDefer回调队列中注册newDefer.notifyWidth
											resolve(maxDepth,deferred,Identity,special),
											resolve(maxDepth,deferred,Thrower,special)
										);

									// Normal processors (resolve) also hook into progress
									}else{

										// ...and disregard older resolution values
										maxDepth++;

										then.call(
											returned,
											resolve(maxDepth,deferred,Identity,special),
											resolve(maxDepth,deferred,Thrower,special),
											resolve(maxDepth,deferred,Identity,deferred.notify)
										);
									}

								// Handle all other returned values
								}else{

									// Only substitute handlers pass on context
									// and multiple values (non-spec behavior)
									if ( handler!==Identity ){
										that=undefined;
										args=[ returned ];
									}

									// 调用新创建的deferred对象notifyWith、resolveWith方法
									// 该deferred对象的promise又是then方法最终返回值,用于递归应用阻塞回调
									// 示例:
									// var promise1=$.ajax(url1);
									// var promise2=promise1.then(function(data){
									//    return $.ajax(url2,{data:data});
									//	});
									//	var promise3=promise2.then(function(data){
									//	    return $.ajax(url3,{data:data});
									//	});
									//	promise3.done(function(data){
									//	    // data retrieved from url3
									//	});
									//	
									//	tuple[i][3]函数队列中添加then方法新建的deferred对象函数队列的启动函数
									( special||deferred.resolveWith )( that||deferred.promise(),args );
								}
							},

							// Only normal processors (resolve) catch and reject exceptions
							process=special ?
								mightThrow :
								function(){
									try{
										mightThrow();
									}catch(e){

										if ( jQuery.Deferred.exceptionHook ){
											jQuery.Deferred.exceptionHook(e,process.stackTrace);
										}

										// Support: Promises/A+ section 2.3.3.3.4.1
										// https://promisesaplus.com/#point-61
										// Ignore post-resolution exceptions
										if ( depth+1>=maxDepth ){

											// Only substitute handlers pass on context
											// and multiple values (non-spec behavior)
											if ( handler!==Thrower ){
												that=undefined;
												args=[e];
											}

											deferred.rejectWith(that||deferred.promise(),args);
										}
									}
								};

						// Support: Promises/A+ section 2.3.3.3.1
						// https://promisesaplus.com/#point-57
						// Re-resolve promises immediately to dodge false rejection from
						// subsequent errors
						if ( depth ){
							process();
						}else{
							// 获取堆栈信息???
							if ( jQuery.Deferred.getStackHook ){
								process.stackTrace=jQuery.Deferred.getStackHook();
							}
							window.setTimeout(process);// 不是立即执行???
						}
					};
				}

				return jQuery.Deferred(function(newDefer){
					// deferred对象的tuples[i][3]添加resovle函数返回的函数队列
					// 通过deferred.notify|resolve|reject方法启动执行,因此是在延迟函数执行过程或完毕以后调用
					tuples[0][3].add(
						resolve(
							0,
							newDefer,
							jQuery.isFunction(onProgress) ? onProgress : Identity,
							newDefer.notifyWith
						)
					);

					tuples[1][3].add(
						resolve(
							0,
							newDefer,
							jQuery.isFunction( onFulfilled ) ? onFulfilled : Identity
						)
					);

					tuples[2][3].add(
						resolve(
							0,
							newDefer,
							jQuery.isFunction( onRejected ) ? onRejected : Thrower
						)
					);
				}).promise();
				// 创建新的deferred对象,链式注册done|fail函数队列
				// 该函数队列的触发函数加载到原有deferred对象的tuple[i][3]中
			},

			// 将promise拷贝给obj,或者返回promise,deferred模块中应用是拷贝给deferred
			promise: function(obj){
				return obj!=null?jQuery.extend(obj,promise):promise;
			}
		},
		deferred={};

	// 以Callbacks构造deferred对象progress、done、fail方法的函数存储域
	// promise.progress|done|fail执行该三组队列函数的注册
	// deferred.notify|resolve|rejeact|deferred.notifyWith|resolveWith|rejeactWith
	// 		即时执行相应的函数队列,并执行tuple[3]中的函数队列(promise.then方法注册)
	// done函数队列率先引入改变state状态值,使fail函数队列失效,progress队列锁定的函数,fail函数相应处理
	// deferred在延时函数内部使用,延时函数返回promise对象,实现外部接口只能注册add、不能启动fire
	jQuery.each(tuples, function(i,tuple){
		var list=tuple[2],// Callbacks对象实例,提供done,fail,progress状态执行函数的添加和触发
			stateString=tuple[5];// progress状态为空,done状态为resolved,fail状态为rejected

		// promise.progress、promise.done、promise.fail方法添加各自状态的回调函数
		// list指向Callbacks对象,以此驻留在内存中
		promise[tuple[1]]=list.add;

		// 向done和fail函数队列添加改变状态值state的函数,state分为pending、resolved、rejected三种
		// 同时done函数队列执行时使fail函数队列失效,progress函数队列锁定
		// fail函数队列执行时使done函数失效,progress函数队列锁定
		if ( stateString ){
			list.add(
				function(){
					state=stateString;
				},
				tuples[3-i][2].disable,// done、fail函数队列交互失效
				tuples[0][2].lock// progress函数队列锁定
			);
		}

		// promise.then方法将函数队列注册在相应的tuple[3]的Callbacks对象里
		// tuple[2]中添加tuple[3].fire,以执行tuple[3]中注册的函数队列
		list.add(tuple[3].fire);

		// deferred.notify触发执行progress状态回调函数,也即promise.progress方法注册的函数
		// deferred.resolve触发执行done状态回调函数,也即promise.done方法注册的函数
		// deferred.reject触发执行fail状态回调函数,也即promise.fail方法注册的函数
		deferred[tuple[0]]=function(){
			deferred[tuple[0]+"With"](this===deferred?promise:this, arguments);
			return this;
		};

		// deferred.notifyWith触发执行progress状态回调函数
		// deferred.resolveWith触发执行done状态回调函数
		// deferred.rejectWith触发执行fail状态回调函数
		deferred[tuple[0]+"With"]=list.fireWith;
	} );

	// 将promise方法拷贝给deferred
	promise.promise(deferred);

	// Deferred函数执行完成后回调函数,func用以改造deferred对象
	if (func){
		func.call(deferred,deferred);
	}

	return deferred;
},

// var dtd=$.Deferred();
// function wait(dtd){
// 		setTimeout(function(){
// 			dtd.resovle()
// 		},3000)
// 		
// 		return dtd.promise()
// }
// $.when(wait(dtd)).done(function(){console.log("success")})
// 
// $.when方法主要意图:多个延迟对象同时执行,最末一个执行完毕,调用done方法注册的函数
// 实际境况:触发函数在$.when(dtd)参数dtd函数中
// 			 函数队列在$.when(dtd).done(fn).fail(fn)中以done|fail方法注册
// 主要问题:dtd.resovle参数传入函数队列
// 			 $.when参数有多个延迟对象[dtd]时,判断dtd执行完毕总时间
// 实现方案:以dtd.resovle方法启动dtd.done()注册的函数队列
// 			 构造新的deferred对象master,且master.promise()作为返回结果
// 			 master.resovle|reject方法加入dtd.done()函数队列,因此dtd.resovle方法同时启动master的函数队列
// 			 同时参数由dtd.resovle传入dtd.done(),最终传入master.done|fail方法中
// 			 多个延迟对象通过闭包外参数remaining判断这几个延迟对象是否执行完毕
// 设计:updateFunc方法在$.when()参数延迟对象执行完毕后调用
// 		     用remaining判断$.when()参数延迟对象是否执行完毕
// 		     remaining==0时触发执行$.when().done()注册的函数队列
// 		 通过条件语句区分$.when()参数为deferred对象,还是普通函数,或者为空
// 		 	 若为延迟对象,通过deferred.done|then注册updateFunc,执行$.when().done()的函数队列
// 		 	 若为普通函数,立即调用updateFunc,执行$.when().done()的函数队列,传参为$.when()中参数
// 		 	 若为空,立即执行$.when().done()的函数队列
when:function(){
	var method,resolveContexts,
		i=0,
		resolveValues=slice.call(arguments),
		length=resolveValues.length,
		remaining=length,
		master=jQuery.Deferred(),// 构造新的deferred对象,$.when各参数deferred对象执行完毕后执行该对象的done|fail方法

		// 通过闭包驻留remaining,用以判断$.when各参数deferred对象均执行完毕
		// 执行完毕通过master.resolveWith方法,执行$.when().done()中done方法注册的函数
		updateFunc=function(i){
			return function(value){// value为$.when各参数deferred对象的resolve|resovleWith方法带给done方法的参数
				resolveContexts[i]=this;
				resolveValues[i]=arguments.length>1 ? slice.call(arguments) : value;
				if ( !(--remaining) ) {
					master.resolveWith(
						resolveContexts.length===1 ? resolveContexts[0] : resolveContexts,
						resolveValues
					);
				}
			};
		};

	if ( length>0 ){
		resolveContexts=new Array(length);// 数组形式存储$.when各参数deferred对象
		for ( ; i<length; i++ ){

			if ( resolveValues[i] && jQuery.isFunction(( method=resolveValues[i].promise )) ){
				// 以done方法将master.resovleWith添入延迟对象dtd.done()函数队列中
				method.call( resolveValues[i] )
					// $.when各参数deferred对象执行promise方法的时候,this关键字指向该deferred对象
					.done( updateFunc(i) )
					// $.when各参数deferred对象均执行成功时,即调用$.when().done()
					// 参数为各参数deferred对象resolve|resovleWith方法添加的参数中done方法注册的函数
					.fail( master.reject );
					// $.when各参数deferred对象有一个执行失败,即调用$.when().fail()中fail方法注册的函数
			}else if( resolveValues[i] && jQuery.isFunction(( method=resolveValues[i].then )) ){
				// 以then方法将master.resovleWith添入延迟对象dtd.done()函数队列中
				method.call( resolveValues[i], updateFunc(i), master.reject);
			}else{
				// 如果参数不是deferred对象,立即调用$.when().done()中done方法注册的函数
				// done方法传入的参数由$.when参数构成
				updateFunc(i)( resolveValues[i] );
			}
		}

	}else{
		// 没有参数,立即调用$.when().done()中done方法注册的函数
		master.resolveWith();
	}

	return master.promise();
}

});

return jQuery;
});

 

  • 描述: def.pipe
  • 大小: 27.9 KB
  • 描述: resovle.fn
  • 大小: 22.1 KB
  • 大小: 32.2 KB
  • 大小: 40.1 KB
分享到:
评论

相关推荐

    谈谈jQuery之Deferred源码剖析

    ### jQuery之Deferred源码剖析知识点 #### 一、Deferred和Promise简介 jQuery的Deferred对象是jQuery在ES6的Promise概念出现之前的一种实现,它允许开发者将异步操作的结果通过链式调用的方式处理,避免了传统的...

    2014-10-16-深入理解jQuery(4)——Deferred1

    2. Deferred源码分析 Deferred模块的框架可以分为两个部分:Deferred方法和when方法。Deferred方法是用于创建Deferred对象的,而when方法是用于管理多个Deferred对象的执行顺序的。 Deferred方法的结构可以分为三...

    Deferred

    在JavaScript编程中,“Deferred”是一个重要的概念,尤其在异步编程和Promise机制中扮演着核心角色。 Deferred对象是jQuery引入的,后来被ES6的Promise所吸收,它为解决回调地狱问题提供了有效的解决方案。这篇博客...

    jQuery源码分析系列.pdf

    - **异步队列Deferred**:探讨jQuery中用于处理异步操作的`Deferred`对象,解释其工作原理和如何实现链式调用。 - **队列Queue**:讲解队列在jQuery动画和AJAX请求中的作用,以及如何管理和控制队列中的任务执行...

    DOOM-3 源码

    4. **延迟渲染(Deferred Shading)**:DOOM-3使用延迟渲染技术,先将所有几何信息存储到G-Buffer,然后在后处理阶段进行光照计算。这种方法允许进行复杂的光照效果,如法线贴图、高光反射等。 三、物理系统 DOOM-...

    vxWorks内核及驱动源码

    2. I/O子系统:I/O请求通过中断处理和DPC(Deferred Procedure Call)进行,确保实时性。 3. 驱动接口:VxWorks提供了标准的设备驱动接口,如open、close、read、write等,方便用户操作硬件设备。 4. 总线驱动:如...

    Windows内核安全与驱动开发 随书源码.rar

    - **Interrupt Service Routines (ISR)** 和 **DPCs (Deferred Procedure Calls)**:ISR处理设备中断,DPC则用于执行非时间敏感的中断后任务。 通过对这些源码的分析和实践,开发者不仅可以提升对Windows内核的理解...

    Windows Research Kernel - WRK源码

    例如,某些内部数据结构,如KPCR(Processor Control Region)和KDPC(Deferred Procedure Call Descriptor),揭示了系统如何管理和调度硬件资源。而未公开的函数则可能涉及到更高级别的系统操作,如安全机制、内存...

    jquery源码框架解读

    6. **性能优化**:jQuery在设计时考虑了性能问题,如使用Sizzle选择器引擎优化DOM查找,使用缓存避免重复操作,以及延迟执行(`.deferred()`)和节流/debounce(防止频繁触发)等技术。 7. **源码结构分析**:jQuery...

    jQuery源码分析之Callbacks详解

    在深入讨论jQuery源码中Callbacks模块的内部实现之前,先要明确什么是Callbacks以及它在JavaScript编程中的作用。Callbacks,即回调函数,在JavaScript中扮演着至关重要的角色。由于JavaScript是基于单线程事件循环...

    jQuery源码分析(1.7)

    ### jQuery源码分析关键知识点详解 #### 一、前言 在深入了解jQuery源码之前,有必要先简要介绍一下jQuery的基本情况及其对JavaScript编程领域的重要意义。jQuery作为一个轻量级、功能丰富的JavaScript库,在Web...

    jquery源码 带格式

    - `readyList`: 用于处理DOM ready事件的递延对象(deferred object)。 - `document`: 当前窗口的文档对象模型(DOM)。 - `location`: 当前窗口的位置信息。 - `navigator`: 浏览器的信息,如名称、版本等。 - `_...

    jQuery源码分析

    jQuery源码分析 00 前言开光 01 总体架构 03 构造jQuery对象-源码结构和核心函数 ...05 异步队列 Deferred 08 队列 Queue 09 属性操作 10 事件处理-Event-概述和基础知识 15 AJAX-前置过滤器和请求分发器

    gwt 揭秘 源码

    GWT提供了多种优化策略,包括代码分割(Code Splitting)以减少初始加载时间,延迟加载(Deferred Binding)根据浏览器特性生成定制的JavaScript,以及压缩和合并JavaScript文件以减小下载体积。 9. **社区与生态*...

    jQuery 源码分析笔记(3) Deferred机制

    jQuery.Deferred()用来构造一个Deferred对象。该对象有状态值,共有三种: Rejected, Resolved和初始状态。其中Resolved表示该操作成功完成了,而Rejected 则表示出现了错误,调用失败。Deferred对象的主要成员如下:...

    window驱动示例源码汇总

    9. **中断处理**:中断服务例程(ISR)是响应硬件中断的函数,用于快速处理中断事件,然后通过DPC(Deferred Procedure Call)或线程上下文完成剩余工作。 10. **调试工具**:如WinDbg,是驱动开发中常用的调试工具...

    Jquery详解-源码分析

    9. **延迟对象(Deferred)**:jQuery引入了 Deferred 对象来处理异步操作,使得回调函数的编写更加清晰和灵活。 10. **性能优化**:jQuery源码中包含了许多性能优化技巧,如缓存查找结果、批量操作和使用`...

Global site tag (gtag.js) - Google Analytics