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