jQuery源码分析-05异步队列 Deferred 使用介绍

5. 异步队列 Deferred
5.1 概述
异步队列是一个链式对象,增强对回调函数的管理和调用,用于处理异步任务。
异步队列有三种状态:初始化(unresolved),成功(resolved),失败(rejected)。
执行哪些回调函数依赖于状态。
状态变为成功(resolved)或失败(rejected)后,将保持不变。
回调函数的绑定可以是同步,也可以是异步的,即可以在任何时候绑定。
(本节中的 绑定 注册 增加 具有相同的含义)
5.2 关键方法
先看看jQuery. Deferred()中的关键方法
分类
方法
说明
增加
deferred.done()
增加成功回调函数
状态为成功(resolved)时立即调用
deferred.fail()
增加失败回调函数
状态为失败(rejected)时立即调用
deferred.then()
增加成功回调函数和失败回调函数到各自的队列中
便捷方法,两个参数可以是数组或null
状态为成功(resolved)时立即调用成功回调函数
状态为失败(rejected)时立即调用失败回调函数
deferred.always()
增加回调函数,同时增加到成功队列和失败队列
状态已确定(无论成功或失败)时立即调用回调函数
执行
deferred.resolve()
调用成功回调函数队列
通过调用deferred.resolveWith()实现
deferred.resolveWith()
使用指定的上下文和参数执行成功回调函数
deferred.reject()
调用失败回调函数队列
通过调用deferred.rejectWith()实现
deferred.rejectWith()
使用指定的上下文和参数执行失败回调函数队列
其他
deferred.isRejected()
判断状态是否为成功(resolved)
deferred.isResolved()
判断状态是否为失败(rejected)
deferred.pipe()
每次调用回调函数之前先调用传入的成功过滤函数或失败过滤函数,并将过滤函数的返回值作为回调函数的参数
最终返回一个只读视图(调用promise实现)
deferred.promise()
返回deferred的只读视图
接下来将会jQuery._Deferred和jQuery.Deferred的源码详细剖析。

5.3 jQuery._Deferred


代码如下:

局部变量
// 参考资料:
// 官网文档 http://api.jquery.com/category/deferred-object/
// Deferred机制 http://www.cnblogs.com/fjzhou/archive/2011/05/30/jquery-source-3.html
// 在jQuery 1.5中使用deferred对象 http://developer.51cto.com/art/201103/248638.htm
// 拿着放大镜看Promise http://www.cnblogs.com/sanshi/archive/2011/03/11/1981789.html
// Promises/A http://wiki.commonjs.org/wiki/Promises/A
var // Promise methods
// 注意,没有以下方法:resolveWith resolve rejectWith reject pipe when cancel
// 即不允许调用resolve reject cancel等
promiseMethods = "done fail isResolved isRejected promise then always pipe".split( " " ),
// Static reference to slice
// 静态引用slice方法,借鸡生蛋
sliceDeferred = [].slice;
_Deferred:
_Deferred: function() {
var // callbacks list
// 回调函数数组(这里不翻译为队列,避免概念上的混淆)
callbacks = [],
// stored [ context , args ]
// 存储上下文、参数,同时还可以标识是否执行完成(fired非空即表示已完成)
// 这里的“完成”指回调函数数组中“已有”的函数都已执行完成;
// 但是可以再次调用done添加回调函数,添加时fired会被重置为0
fired,
// to avoid firing when already doing so
// 如果已经触发正在执行,避免再次触发
firing,
// flag to know if the deferred has been cancelled
// 标识异步队列是否已被取消,取消后将忽略对done resolve resolveWith的调用
cancelled,
// 异步队列定义(这才是正主,上边的局部变量通过闭包引用)
// the deferred itself
deferred = {
// done( f1, f2, ...)
// 增加成功回调函数,状态为成功(resolved)时立即调用
done: function() {
// 如果已取消,则忽略本次调用
if ( !cancelled ) {
// 将后边代码用到的局部变量定义在代码块开始处的好处:
// 1.声明变量,增加代码可读性;
// 2.共享变量,提高性能
// 注:多年写Java的经验,养成了全局变量在开头、临时变量随用随定义的习惯,看来JavaScript有些不同
var args = arguments, // 回调函数数组
i, // 遍历变量
length, // 回调函数数组长度
elem, // 单个回调函数
type, // elem类型
_fired; // 用于临时备份fired(fired中存储了上下文和参数)
// 如果已执行完成(即fired中保留了上下文和参数)
// 则备份上下文和参数到_fired,同时将fired置为0
if ( fired ) {
_fired = fired;
fired = 0;
}
// 添加arguments中的函数到回调函数数组
for ( i = 0, length = args.length; i < length; i++ ) {
elem = args[ i ];
type = jQuery.type( elem );
// 如果是数组,则递归调用
if ( type === "array" ) {
// 强制指定上下文为deferred,个人认为这里没必要指定上下文,因为默认的上下文即为deferred
deferred.done.apply( deferred, elem );
} else if ( type === "function" ) {
callbacks.push( elem );
}
}
// 如果已执行(_fired表示Deferred的状态是确定的),则立即执行新添加的函数
// 使用之前指定的上下文context和参数args
if ( _fired ) {
deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] );
}
}
return this;
},
// resolve with given context and args
// 执行,使用指定的上下文和参数
resolveWith: function( context, args ) {
// 满足以下全部条件,才会执行:没有取消 没有正在执行 没有执行完成
// 如果已取消 或 已执行完成 或 正在执行,则忽略本次调用
if ( !cancelled && !fired && !firing ) {
// make sure args are available (#8421)
// 确保args可用,一个避免null、undefined造成ReferenceError的常见技巧
args = args || [];
// 执行过程中将firing改为1
firing = 1;
try {
// 遍历动态数组的技巧
while( callbacks[ 0 ] ) {
// 注意这里使用指定的context,而不是this
callbacks.shift().apply( context, args );
}
}
// JavaScript支持try/catch/finally
finally {
fired = [ context, args ];
firing = 0;
}
}
return this;
},
// resolve with this as context and given arguments
// 把状态设置为Resolved
// 设置的理解不准确,因为是否Resolved,是调用isResolved判断firing、fired的状态得到的。
// 可以理解为执行
resolve: function() {
deferred.resolveWith( this, arguments );
return this;
},
// Has this deferred been resolved?
// 是否已执行(或解决)?
// 在执行或已执行完毕,都认为已执行/解决
// “已”可能不准确,因为执行过程中也认为是已执行
isResolved: function() {
// 正在运行中
// 或
// 已运行完(即fired不为空/0)
return !!( firing || fired );
},
// Cancel
// 取消异步队列
// 设置标记位,清空函数队列
cancel: function() {
cancelled = 1;
callbacks = [];
return this;
}
};
return deferred;
}

5.4 jQuery.Deferred


代码如下:

// Full fledged deferred (two callbacks list)
// 创建一个完整的异步队列(包含两个回调函数数组)
// 异步队列有三种状态:初始化(unresolved),成功(resolved),失败(rejected)。
// 执行哪些回调函数依赖于状态。
// 状态变为成功(resolved)或失败(rejected)后,将保持不变。
Deferred: function( func ) {
// _Deferred本无成功状态或失败状态,有四种状态:初始化、执行中、执行完毕、已取消
// 为了代码复用, 内部先实现了一个_Deferred
// failDeferred通过闭包引用
var deferred = jQuery._Deferred(),
failDeferred = jQuery._Deferred(),
promise;
// Add errorDeferred methods, then and promise
jQuery.extend( deferred, {
// 增加成功回调函数和失败回调函数到各自的队列中
// 便捷方法,两个参数可以是数组或null
// 状态为成功(resolved)时立即调用成功回调函数
// 状态为失败(rejected)时立即调用失败回调函数
then: function( doneCallbacks, failCallbacks ) {
// 上下文在这里有切换:虽然done返回的是deferred,但是fail指向failDeferred.done,执行fail是上下文变为failDeferred
// 简单点说就是:
// 调用done时向deferred添加回调函数doneCallbacks
// 调用fail时向failDeferred添加回调函数failCallbacks
// 因此这行表达式执行完后,返回的是failDeferred
deferred.done( doneCallbacks ).fail( failCallbacks );
// 强制返回deferred
return this;
},
// 注册一个callback函数,无论是resolved或者rejected都会被 调用。
// 其实,是把传入的函数(数组),同时添加到deferred和failDeferred
// 并没有像我想象的那样,存到单独的函数数组中
always: function() {
// done的上下文设置为deferred,fail的上下文设置为this
// done和fail的上下文不一致吗?一致!在这里this等于deferred
// 但是这里如此设置上下文应该该如何解释呢?与then的实现有什么不一样呢?
// fail指向fail指向failDeferred.done,默认上下文是failDeferred,failDeferred的回调函数数组callbacks是通过闭包引用的,
// 这里虽然将failDeferred.done方法的上下文设置为deferred,但是不影响failDeferred.done的执行,
// 在failDeferred.done的最后将this替换为deferred,实现链式调用,
// 即调用过程中没有丢失上下文this,可以继续链式调用其他的方法而不会导致this混乱
// 从语法上,always要达到的效果与then要达到的效果一致
// 因此,这行代码可以改写为两行(类似then的实现方式),效果是等价的:
// deferred.done( arguments ).fail( arguments );
// returnr this;
return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments );
},
// 增加失败回调函数
// 状态为失败(rejected)时立即调用
fail: failDeferred.done,
// 使用指定的上下文和参数执行失败回调函数队列
// 通过调用failDeferred.rejectWith()实现
rejectWith: failDeferred.resolveWith,
// 调用失败回调函数队列
// 通过调用failDeferred.resolve()实现
reject: failDeferred.resolve,
// 判断状态是否为成功(resolved)
isRejected: failDeferred.isResolved,
// 每次调用回调函数之前先调用传入的成功过滤函数或失败过滤函数,并将过滤函数的返回值作为回调函数的参数
// 最终返回一个只读视图(调用promise实现)
// fnDone在状态是否为成功(resolved)时被调用
// fnFail在状态是否为失败(rejected)时被调用
// 关于其他的解释:
// 1. 有的文章翻译为“管道机制”,从字面无法理解要表达什么含义,因此至少是不准确
// 2. 错误理解:所谓的pipe,只是把传入的fnDone和fnFail放到了成功队列和失败队列的数组头部
pipe: function( fnDone, fnFail ) {
return jQuery.Deferred(function( newDefer ) {
jQuery.each( {
done: [ fnDone, "resolve" ], // done在后文中会指向deferred.done
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
// 返回的是一个不完整的Deferred的接口,没有resolve和reject,即不能 修改Deferred对象的状态,
// 这是为了不让外部函数提早触发回调函数,可以看作是一种只读视图。
//
// 比如$.ajax在1.5版本后不再返回XMLHttpRequest,而是返回一个封装了 XMLHttpRequest和Deferred对象接口的object。
// 其中Deferred部分就是promise()得到 的,这样不让外部函数调用resolve和reject,防止在ajax完成前触发回调函数。
// 把这两个函数的调用权限保留给ajax内部。
promise: function( obj ) {
if ( obj == null ) {
// 实际只会执行一次promise,第一次执行的结果被存储在promise变量中
if ( promise ) {
return promise;
}
promise = obj = {};
}
var i = promiseMethods.length;
// 又一种循环遍历方式
// 我习惯用:
// for( i = 0; i < len; i++ ) 或 for( i = len-1; i >=0; i-- ) 或 for( i = len; i--; )
// jQuery真是遍地是宝!
while( i-- ) {
obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ];
}
return obj;
}
});
// Make sure only one callback list will be used
// 成功队列执行完成后,会执行失败带列的取消方法
// 失败队列执行完成后,会执行成功队列的取消方法
// 确保只有一个函数队列会被执行,即要么执行成功队列,要么执行失败队列;
// 即状态只能是或成功、或失败,无交叉调用
// deferred和failDeferred的canceled属性,只能通过闭包引用,因此不用担心状态、上下文的混乱
deferred.done( failDeferred.cancel ).fail( deferred.cancel );
// Unexpose cancel
// 隐藏cancel接口,即无法从外部取消成功函数队列
delete deferred.cancel;
// Call given func if any
// 执行传入的func函数
if ( func ) {
func.call( deferred, deferred );
}
return deferred;
}

5.5 jQuery.when


代码如下:

// Deferred helper
// 异步队列工具函数
// firstParam:一个或多个Deferred对象或JavaScript普通对象
when: function( firstParam ) {
var args = arguments,
i = 0,
length = args.length,
count = length,
// 如果arguments.length等于1,并且firstParam是Deferred,则deferred=firstParam
// 否则创建一个新的Deferred对象(如果arguments.length等于0或大于1,则创建一个新的Deferred对象)
// 通过jQuery.isFunction( firstParam.promise )简单的判断是否是Deferred对象
deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
firstParam :
jQuery.Deferred();
// 构造成功(resolve)回调函数
function resolveFunc( i ) {
return function( value ) {
// 如果传入的参数大于一个,则将传入的参数转换为真正的数组(arguments没有slice方法,借鸡生蛋)
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对象
deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) );
}
};
}
// 如果参数多于一个
if ( length > 1 ) {
for( ; i < length; i++ ) {
// 简单的判断是否是Deferred对象,是则调用.promise().then(),否则忽略
if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) {
// 增加成功回调函数和失败回调函数到各自的队列中
args[ i ].promise().then( resolveFunc(i), deferred.reject );
} else {
// 计数器,表示发现不是Deferred对象,而是普通JavaScript对象
--count;
}
}
// 计数器为0时,表示传入的参数都不是Deferred对象
// 执行成功回调函数队列,上下文强制为传入的第一个Deferred对象
if ( !count ) {
deferred.resolveWith( deferred, args );
}
// deferred !== firstParam,即deferred为新创建的Deferred对象
// 即length == 0
} else if ( deferred !== firstParam ) {
// 执行成功回调函数队列,上下文强制为新创建的Deferred对象
deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
}
// 返回传入的第一个Deferred或新创建的Deferred对象的只读视图
return deferred.promise();
}

5.6 Deferred应用
l jQuery.ajax()
n TODO
5.7 可以学习的技巧
l 闭包


代码如下:

function a(){
var guid = 1;
return function(){
return guid++;
}
}
var defer = a();
console.info( defer() ); // 1
console.info( defer() ); // 2
console.info( defer() ); // 3
console.info( defer() ); // 4

l 避免null、undefined造成ReferenceError的常见技巧
args = args || [];
l 遍历动态数组的技巧
while( callbacks[ 0 ] ) {
callbacks.shift().apply( context, args );
}
l try/catch/finally 实现错误处理
语法
说明
try {
// tryStatements
} catch( exception ) {
// catchStatements
} finally {
// finallyStatements
}
tryStatements
必选项。
可能发生错误的语句。
exception
必选项。任何变量名。
exception 的初始化值是扔出的错误的值。
catchStatements
可选项。
处理在相关联的 tryStatement 中发生的错误的语句。
finallyStatements
可选项。
在所有其他过程发生之后无条件执行的语句。
l 链式对象:通过返回this实现链式调用
方法
返回值
done
this(即deferred)
resolveWith
this(即deferred)
resolve
this(即deferred)
cancel
this(即deferred)
l 代码复用 $.each
jQuery.each( {
done: [ fnDone, "resolve" ], // done在后文中会指向deferred.done
fail: [ fnFail, "reject" ]
}, function( handler, data ) {
// 公共代码复用
});
5.8 后续
l Deferred在jQuery中的应用
l Deferred的自定义应用

(0)

相关推荐

  • jQuery中的deferred对象和extend方法详解

    1 deferred对象 deferred对象是jQuery的回调函数解决方案,它是从jQuery1.5.0版本开始引入的功能 deferred对象的方法 (1) $.Deferred() 生成一个deferred对象. (2) deferred.done() 指定操作成功时的回调函数 (3) deferred.fail() 指定操作失败时的回调函数 (4) deferred.promise() 没有参数时,返回一个新的deferred对象,该对象的运行状态无法被改变:接受参数时,作用为在参数对

  • jQuery.extend()的实现方式详解及实例

    复制代码 代码如下: <script type="text/javascript" src="jquery-1.5.2.js"></script><script>obj1 = { a : 'a', b : 'b' };obj2 = {  x : { xxx : 'xxx', yyy : 'yyy' },  y : 'y' }; $.extend(true, obj1, obj2); alert(obj1.x.xxx);  // 得

  • jQuery $.extend()用法总结

    jQuery为开发插件提拱了两个方法,分别是: jQuery.fn.extend(object); jQuery.extend(object); jQuery.extend(object);为扩展jQuery类本身.为类添加新的方法. jQuery.fn.extend(object);给jQuery对象添加方法.这个应该很好理解吧.举个例子. 复制代码 代码如下: <span style="font-size:18px;"><html> <head>

  • 利用jQuery的deferred对象实现异步按顺序加载JS文件

    前段时间看了阮一峰的jQuery的deferred对象详解一文,对jQuery中的deferred的用法了一些了解,今天看到园子里的一篇文章:关于重构JS前端框架的失败经验(顺便怀念那些死去的代码),于是把我之前写的一个利用jQuery的deferred异步按顺序加载JS文件方案分享出来,欢迎指正. 如果你现在对jQuery中的deferred还不了解,强烈建议你看一下阮一峰的jQuery的deferred对象详解一文. 加载JS文件的代码如下: 复制代码 代码如下: /* Loading Ja

  • jQuery之Deferred对象详解

    deferred对象是jQuery对Promises接口的实现.它是非同步操作的通用接口,可以被看作是一个等待完成的任务,开发者通过一些通过的接口对其进行设置.事实上,它扮演代理人(proxy)的角色,将那些非同步操作包装成具有某些统一特性的对象,典型例子就是Ajax操作.网页动画.web worker等等. jQuery的所有Ajax操作函数,默认返回的就是一个deferred对象. Promises是什么 由于JavaScript单线程的特点,如果某个操作耗时很长,其他操作就必需排队等待.为

  • jQuery.extend 函数详解

    JQuery的extend扩展方法: Jquery的扩展方法extend是我们在写插件的过程中常用的方法,该方法有一些重载原型,在此,我们一起去了解了解. 一.Jquery的扩展方法原型是: 复制代码 代码如下: extend(dest,src1,src2,src3...); 它的含义是将src1,src2,src3...合并到dest中,返回值为合并后的dest,由此可以看出该方法合并后,是修改了dest的结构的.如果想要得到合并的结果却又不想修改dest的结构,可以如下使用: 复制代码 代码

  • jQuery Deferred和Promise创建响应式应用程序详细介绍

    这篇文章,我们一起探索一下 JavaScript 中的 Deferred 和 Promise 的概念,它们是 JavaScript 工具包(如Dojo和MochiKit)中非常重要的一个功能,最近也首次亮相于 流行的 JavaScript 库 jQuery(已经是1.5版本的事情了). Deferred 提供了一个抽象的非阻塞的解决方案(如 Ajax 请求的响应),它创建一个 "promise" 对象,其目的是在未来某个时间点返回一个响应.如果您之前没有接触过 "promis

  • jQuery的deferred对象使用详解

    这个功能很重要,未来将成为jQuery的核心方法,它彻底改变了如何在jQuery中使用ajax.为了实现它,jQuery的全部ajax代码都被改写了. 但是,它比较抽象,初学者很难掌握,网上的教程也不多.所以,我把自己的学习笔记整理出来了,希望对大家有用. 本文不是初级教程,针对的读者是那些已经具备jQuery使用经验的开发者.如果你想了解jQuery的基本用法,请阅读我编写的<jQuery设计思想>和<jQuery最佳实践>. 一.什么是deferred对象? 开发网站的过程中,

  • 原生js实现复制对象、扩展对象 类似jquery中的extend()方法

    jq的extend()方法能很方便的实现扩展对象方法,语法如下:$.extend(obj1,boj2,obj3); 现在要实现的是:原生js实现复制对象,扩展对象,类似jq中的extend()方法,具体实例如下: 现有3个对象字面量: var o1={hello:1,old:555}, o2 = { abc: 55555555, hello: 2, fun: function() { alert(111); } }, o3={third:9999}; 实现目标: 复制o1对象,把 o2,o3的对

  • jQuery插件开发的两种方法及$.fn.extend的详解

    jQuery插件开发分为两种: 1 类级别 类级别你可以理解为拓展jquery类,最明显的例子是$.ajax(...),相当于静态方法. 开发扩展其方法时使用$.extend方法,即jQuery.extend(object); 复制代码 代码如下: $.extend({ add:function(a,b){return a+b;} , minus:function(a,b){return a-b;} }); 页面中调用: 复制代码 代码如下: var i = $.add(3,2); var j

随机推荐