jQuery异步框架探究2:jQuery.Deferred方法

Posted 程序猿子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jQuery异步框架探究2:jQuery.Deferred方法相关的知识,希望对你有一定的参考价值。

(本文针对jQuery1.6.1版本)关于Deferred函数的描述中有一个词是fledged,意为"羽翼丰满的",说明jQuery.Deferred函数应用应该更成熟。这个函数与jQuery._Deferred函数有密不可分的关系。

1 内部实现

	Deferred: function( func ) {
		var deferred = jQuery._Deferred(),
			failDeferred = jQuery._Deferred(),
			promise;
		// Add errorDeferred methods, then and promise
		jQuery.extend( deferred, {
			then: function( doneCallbacks, failCallbacks ) {
				deferred.done( doneCallbacks ).fail( failCallbacks );
				return this;
			},
			always: function() {
				return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments );
			},
			fail: failDeferred.done,
			rejectWith: failDeferred.resolveWith,
			reject: failDeferred.resolve,
			isRejected: failDeferred.isResolved,
			pipe: function( fnDone, fnFail ) {
				return jQuery.Deferred(function( newDefer ) {
					jQuery.each( {
						done: [ fnDone, "resolve" ],
						fail: [ fnFail, "reject" ]
					}, function( handler, data ) {
						var fn = data[ 0 ],
							action = data[ 1 ],
							returned;
						if ( jQuery.isFunction( fn ) ) {
							deferred[ handler ](function() {
								returned = fn.apply( this, arguments );
								if ( returned && jQuery.isFunction( returned.promise ) ) {
									returned.promise().then( newDefer.resolve, newDefer.reject );
								} else {
									newDefer[ action ]( returned );
								}
							});
						} else {
							deferred[ handler ]( newDefer[ action ] );
						}
					});
				}).promise();
			},
			// Get a promise for this deferred
			// If obj is provided, the promise aspect is added to the object
			promise: function( obj ) {
				if ( obj == null ) {
					if ( promise ) {
						return promise;
					}
					promise = obj = {};
				}
				var i = promiseMethods.length;
				while( i-- ) {
					obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ];
				}
				return obj;
			}
		});
		// Make sure only one callback list will be used
		deferred.done( failDeferred.cancel ).fail( deferred.cancel );
		// Unexpose cancel
		delete deferred.cancel;
		// Call given func if any
		if ( func ) {
			func.call( deferred, deferred );
		}
		return deferred;
	},

其实一张图就能很好的说明调用jQuery.Deferred()函数之后返回的对象内部结构。
技术分享
调用jQuery.Deferred()函数返回的也是一个异步对象,但是这个异步对象相比较jQuery._Deferred()返回对象是属于增强型的,这个增强的异步对象在内部非常巧妙的使用了两个普通异步对象deferred和failDeferred,之所以不叫两个"异步队列",是因为那样会简单化这两个异步对象的真实结构--它们根本就不是一种数据结构,而是一系列特殊数据(闭包中的四个变量)和操作(五个方法)的集合,上一篇已详细展开描述过。

这两个异步对象在jQuery.Deferred函数内初始化的时候完全相同:分别调用了一次jQuery._Deferred()函数而获得两个新创建的独立的异步对象,其实这个时候它们并没有地位上的差异,就像一对孪生兄弟一样--身高一致,体重一致。但是接下来就厚此薄彼了--只对其中一个对象deferred扩展属于另一个对象failDeferred中的属性,扩展属性的一方deferred成为大哥,被共享属性免费提供服务的一方failDeferred成为小弟。函数最后返回的引用只有大哥deferred,大哥出人头地了,小弟被成为踏脚石永远暗无天日了(由于有大哥对他属性的引用,自动垃圾回收收不走他,真是想shi都无门的弟弟)。

从外部使用的角度看,只有一个增强型异步对象deferred,但是这个对象具备了两个弹夹、两个扳机,这在实际生活中貌似还没有哪位设计师实现过这样的武器(具有双管的枪型倒有不少),未来单兵武器系统--枪榴弹与突击步枪合二为一的模型与此有部分相似。

通过扩展一套对立的操作增强一个普通异步对象到增强型异步对象的目的是增加更多的回调钩子应对更多的场景,这里通过第二个普通异步对象扩展了一套reject操作,事实上,我们当然可以增加更多的普通异步对象扩展更多的操作,比如1.7版本后又引入一套progess操作以说明场景不是只有成功和失败,还有处理中呢。

Deferred函数内部执行了这样一句代码"deferred.done( failDeferred.cancel ).fail( deferred.cancel );",给增强的异步对象deferred提前提供两颗子弹,且这两个函数分别是兄弟俩的cancel函数,这将会达到一个互斥的效果--当调用方内部执行成功时可以触发射击deferred的弹夹callbacks,这个弹夹中的第一颗子弹(callbacks.shift())就是failDeferred对象的cancel方法,击发这颗子弹就锁死failDeferred对象了(failDeferred弹夹的其他子弹将不能被击发了);当调用方内部执行失败时可以触发射击failDeferred的弹夹callbacks,这个弹夹中的第一颗子弹(callbacks.shift())就是deferred对象的cancel方法,击发这颗子弹就锁死deferred对象了(deferred弹夹的其他子弹将不能被击发了)。之所以不说"一定"而是用"可以"这种描述,是因为两个弹夹可以互换使用的,只是约定failDeferred对象的弹夹装失败场景下的触发函数集而已。

对增强型异步对象的扩展到这里貌似已经完美了,该有的更多的回调钩子都有了,但是等等,想想还有没有其他问题?第一篇文章讲过,普通异步对象的五个方法中有一个全能保险:cancelled和cancel方法,调用cancel方法后整个异步对象彻底锁死不能工作了,而增强型异步对象虽然没有引用弟弟的cancel操作,但是自己本身也有这个方法,所以为了避免让调用方随意关闭这个保险,jQuery从返回的哥哥对象中删除这个操作了"delete deferred.cancel;",这又相当于对哥哥的弱化处理(不能给它太多特权)。虽然外部不能调用这个操作了,但是增强型异步对象已经预先将cancel操作作为两颗子弹压入到弹仓了,这就是上一段代码的作用,还有一个基础事实是删除的只是引用,并没有删除cancel方法本身。

上面分析的都是jQuery.Deferred()这种调用,这个函数也可以接受一个函数参数,这个参数函数只被传入一个参数--增强的异步对象自身,至于函数内部怎么处理增强异步对象,完全由调用方自主决策--给了调用方在使用这把枪之前根据自己的喜好先设置一下它的机会,比如典型的初始化(可以在这里装入子弹)等,反正调用方已不能关闭它的保险了。

2 对jQuery._Deferred()扩展的promise()函数


这个方法需要单独讲讲。

前面说过对增强型异步对象有扩展增强也有收缩减弱,这个状态的增强型异步对象功能已经是完备的了,用于jQuery内部其他模块已经没有问题了,但是如果把这个异步对象直接交给不熟悉手枪操作的用户还是可能会引起误操作--虽然用户无法关闭保险,但是可能会在不恰当的时机执行"开枪射击"的操作(调用resolveWith方法或rejectWith方法),射击的触发时机一般是有严格约束的,它往往与具体情境有关,比如浏览器加载dom只有完全完成时才可以触发相关指定事件执行,所以为了防止用户在错误的时间执行错误的事件,可以约束用户只要他提供具体的回调函数即可,实际的触发操作由jQuery自己决定,这样提供给用户的异步对象就不能有"开枪"的操作,为了实现这个目的,在增强型异步对象上扩展了一个promise方法,调用该方法返回增强型异步对象的一个傀儡,而不是增强型异步对象本身,这个傀儡只允许以下操作集:‘promiseMethods = "done fail isResolved isRejected promise then always pipe".split( " " )‘,由于傀儡对应操作引用指向增强型异步对象对应相同的操作方法,所以设置这个傀儡的回调函数的同时对原增强型异步对象的回调函数的设置同时生效。

当然promise方法也可以接受一个参数,提供这个参数的话就把相关设置方法赋给这个参数对象,而不是给傀儡了。

补充一句,熟悉设计模式的同学一定能准确说出promise方法使用的是什么设计模式。

3 对jQuery._Deferred()扩展的pipe()函数


这个pipe方法是非常挑战智力的一个方法,真正的"asynchronize"这个概念的实现就足够先进了(想想java中从bio到nio花了几个版本和多长时间,从nio到aio又花了几个版本和多长时间),再加上"pipe"这个概念也不是那么容易的(参照netty pipeline),所以彻底理解这个方法是相当费脑细胞的,由于后续1.7以后的版本中这个方法过时了,这里就不展开分析了。

最后总结一下,第一篇通过"手枪"模型详细解释了jQuery._Deferred函数的内部原理,本篇通过"未来单兵武器系统"模型详细解释jQuery.Deferred函数的内部原理,可以看到增强型异步对象本身并没有引入其他过多复杂的机制,只是通过增强或减弱普通异步对象的功能来达到设计的目的,因此重点还是jQuery._Deferred函数。另外增强型异步对象虽然解决了"增加更多的回调钩子应对不同场景下的回调逻辑"这个需求,但是jQuery整个异步机制还缺少最后一个环节--"什么时候由谁开枪",更通俗的说是怎么应用于实际场景中,这个主题在下一篇讲解jQuery.when函数时展开(上面对promise方法的解析中提到过一部分)。


最后再次郑重申明:本系列文章未经许可,严禁转载。

































以上是关于jQuery异步框架探究2:jQuery.Deferred方法的主要内容,如果未能解决你的问题,请参考以下文章

jQuery异步框架探究1:jQuery._Deferred方法

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

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

jQuery异步框架探究2:jQuery.Deferred方法

jQuery异步框架探究2:jQuery.Deferred方法

jQuery action类型实例方法探究:Array转换