jQuery异步框架探究3:jQuery.when方法

(本篇文章针对jQuery1.6.1版本)经过前两篇文章对jQuery异步回调机制的详细分析,关于jQuery如何实现异步回调机制的原理已经非常清楚了--将"回调函数"与"击发动作"两个步骤分开,这样可以先把回调函数作为子弹预先存储到弹夹中,由指定对象在"特定条件"实现后再击发子弹。异步对象的done、fail、then、always方法都是存储回调函数的,异步对象的resolveWith、resolve、rejectWith、reject都是执行击发动作的,(特殊调用时序下也可以由前面四个方法执行空仓挂机复位射击操作)。本篇来详细讲讲"特定条件"有哪些。

1 window.setTimeout()方法触发


这个场景的"特定条件"是time out,也就是一个定时器经过设定的时间后触发"开枪射击",一个简单的例子即可说明。
	var gun = jQuery._Deferred();
	gun.done(function(){
		console.info("time out,then execute this function! time is : ", new Date().getTime());
	});
	window.setTimeout(function(){
		gun.resolve();
	}, 5000);
	console.info("now time is : ", new Date().getTime());

当然,为了说明参数和特定对象,我们可以再设计一个简单的例子。

	var obj = {
		p1:function(a){console.info("obj.p1,a is '",a,"'");},
		p2:function(a,b){console.info("obj.p2,a is '",a,"', b is '", b, "'");}
	};
	var gun2 = jQuery.Deferred();
	gun2.done(obj.p1, obj.p2, function(a,b){
		this.p1(a);
		this.p2(a,b);
	});
	window.setTimeout(function(){
		gun2.resolveWith(obj, ["the article author is warhin.","who is warhin?","he is a diaosi!"]);
	}, 5000);
	console.info("now time is : ", new Date().getTime());

2 浏览器加载文档dom对象树状态改变时触发


这个场景的"特定条件"是document状态改变,只要浏览器在加载dom过程中状态发生改变时(不限于文档加载完成状态)都可以触发"击发动作",一个超大的html文档或者用破网速访问一个破网站时示例会比较清晰,这里给出一个简单的例子。
	<html>
	<head>
	<script type="text/javascript" src="http://code.jquery.com/jquery-1.6.1.min.js"></script>
	<script type="text/javascript">
	(function(){
		var gun = jQuery.Deferred();
		gun.done(function(){
			var h1 = $("h1");
			if (h1[0]) {
				h1.bind("click", function(){
					$("div").first().show();
				});
			}
		});
		if ( document.addEventListener ) {
			document.addEventListener( "DOMContentLoaded", f, false );
		} else if ( document.attachEvent ) {
			document.attachEvent( "onreadystatechange", f );
		}
		function f() {
			alert("document.readyState : "+document.readyState);
			gun.resolve();
		}
	})();
	</script>
	</head>
	<body>
		<h1>点击我看看</h1>
		<div style='display:none'>其实我早就潜伏在这里了......</div>
	</body>
	</html>
jQuery与dom加载事件相关的模块就是用的jQuery异步回调机制,后面专门开辟文章详细分析。

3 长时间CPU计算结束或网络交互响应时触发


这个场景的"特定条件"是计算结束或者网络响应状态改变(失败、进行中、成功等),CPU计算的样例下面给出一个,后一种即ajax,关于ajax也单独讲解。
	var gun = jQuery.Deferred();
	gun.done(function(){
		console.info("终于计算结束了");
	});
	(function(){
		for (var i = 0; i <10000; i++) {
			(i+1) * (i+2);
		}
		console.info("trigger");
		gun.resolve();
	})();

4 探究jQuery.when

	// Deferred helper
	when: function( firstParam ) {
		var args = arguments,
			i = 0,
			length = args.length,
			count = length,
			deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
				firstParam :
				jQuery.Deferred();
		function resolveFunc( i ) {
			return function( value ) {
				args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
				if ( !( --count ) ) {
					// Strange bug in FF4:
					// Values changed onto the arguments object sometimes end up as undefined values
					// outside the $.when method. Cloning the object into a fresh array solves the issue
					deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) );
				}
			};
		}
		if ( length > 1 ) {
			for( ; i < length; i++ ) {
				if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) {
					args[ i ].promise().then( resolveFunc(i), deferred.reject );
				} else {
					--count;
				}
			}
			if ( !count ) {
				deferred.resolveWith( deferred, args );
			}
		} else if ( deferred !== firstParam ) {
			deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
		}
		return deferred.promise();
	}
网上几乎大部分人对这个函数的讲解都不是很清晰,很多同学(包括所谓大神)的思维都是"按照我喜欢的想使用的方式调用这个方法",而不是"按照这个方法的api说明来遵守调用",自以为是的结果肯定是缘木求鱼、刻舟求剑,甚至通篇解释都跟这个方法的内部实现、参数使用和设计原理毫无关系。

如果用类似的实现来类比jQuery.when这个方法,那么java.util.concurrent.CyclicBarrier无疑是最贴切的。其思想简而言之就是"只有当所有线程都到达闸门时才触发开闸放水的动作",复杂言之就是这个方法接受两把或以上的"手枪"配合内部一把"手枪"一起使用(这把内部的枪将要导出其傀儡接受子弹),当传递的多把手枪都被击发后(根据各自不同的使用场景)才可以击发最后的那把枪。这也就是为什么这个方法名字要叫when并需要配合增强异步对象傀儡的then/done/fail/always方法一起使用的原因了:"when A1 and A2 [and A3...] then B",只有A1到An这一系列"特定条件"实现了才可以触发B的执行。

这里不继续展开其内部原理了(涉及到复杂的闭包等),只列举一个简单的例子说明(实际组合使用以及参数调用和传递情况可以很复杂)。
	var gun1 = jQuery.Deferred();
	gun1.done(function(){
		console.info("g1");
	});
	var gun2 = jQuery.Deferred();
	gun2.done(function(){
		console.info("g2");
	});
	var gun3 = jQuery.Deferred();
	gun3.done(function(){
		console.info("g3");
	});
	jQuery.when(gun1,gun2,gun3).done(function(){
		console.info('最后成功!');
	}).fail(function(){
		console.info('最后失败!');
	}).always(function(){
		console.info('总是执行!');
	});
	window.setTimeout(function(){
		console.info("gun1 triggered!");
		gun1.resolve();
		window.setTimeout(function(){
			console.info("gun2 triggered!");
			gun2.resolve();
		}, 3000);
	}, 5000);
	jQuery.ajax("http://www.zhihu.com/").always(function(){
		console.info("gun3 triggered!");
		gun3.resolve();
	});

最后再纠正一个观点,严格来说javascript本身应该并没有真正的异步机制(在它的单线程执行环境下),但是对其回调机制可以非常灵活的实现,比如jQuery这样实现的,所以这几篇文章的标题其实应该是--jQuery回调机制探究。


重复申明:未经许可,严禁转载!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值