Deferred
模块也不是必备的模块,可是 ajax
模块中,要用到 promise
风格,必需引入 Deferred
模块。Deferred
也用到了上一篇文章《读Zepto源码之Callbacks模块 )》介绍的 Callbacks
模块。javascript
读 Zepto 源码系列文章已经放到了github上,欢迎star: reading-zeptohtml
本文阅读的源码为 zepto1.2.0java
规范的具体内容能够参考《Promises/A+》 和对应的中文翻译 《Promise/A+规范》,这里只简单总结一下。jquery
promise
是一个包含兼容 promise
规范的函数或对象,promise
包含三种状态 pending
进行中、fulfilled
已完成, rejected
被拒绝,而且必须处于其中一种状态。git
pending
状态能够转换成 fulfilled
状态或者 rejected
状态,可是 fulfilled
状态和 rejected
状态不能再转换成其余状态。github
promise
必须包含 then
方法,then
方法能够接收两个参数,参数类型都为函数,分别为状态变为 fulfilled
后调用的 onFulfilled
函数和 rejected
后调用的 onRejected
函数。ajax
大体了解 Promise/A+
规范后,对后面源码的阅读会有帮助。segmentfault
;(function($){
function Deferred(func) {
deferred = {}
if (func) func.call(deferred, deferred)
return deferred
}
return $.Deferred = Deferred
})(Zepto)复制代码
从上面的精简的结构能够看出,Deferred
是一个函数,函数的返回值是一个符合 Promise/A+
规范的对象,若是 Deferred
有传递函数做为参数,则以 deferred
做为上下文,以 deferred
做为参数执行该函数。数组
var tuples = [
// action, add listener, listener list, final state
[ "resolve", "done", $.Callbacks({once:1, memory:1}), "resolved" ],
[ "reject", "fail", $.Callbacks({once:1, memory:1}), "rejected" ],
[ "notify", "progress", $.Callbacks({memory:1}) ]
],
state = "pending",
promise = {
...
}
deferred = {}
$.each(tuples, function(i, tuple){
var list = tuple[2],
stateString = tuple[3]
promise[tuple[1]] = list.add
if (stateString) {
list.add(function(){
state = stateString
}, tuples[i^1][2].disable, tuples[2][2].lock)
}
deferred[tuple[0]] = function(){
deferred[tuple[0] + "With"](this === deferred ? promise : this, arguments)
return this
}
deferred[tuple[0] + "With"] = list.fireWith
})复制代码
promise
包含执行方法 always
、then
、done
、 fail
、progress
和辅助方法 state
、 promise
等deferred
除了继承 promise
的方法外,还增长了切换方法, resolve
、resoveWith
、reject
、 rejectWith
、notify
、 notifyWith
。$.each(tuples, function(i, tuple){
...
})复制代码
方法的生成,经过遍历 tuples
实现promise
var list = tuple[2],
stateString = tuple[3]
promise[tuple[1]] = list.add复制代码
list
是工厂方法 $.Callbacks
生成的管理回调函数的一系列方法。具体参见上一篇文章《读Zepto源码之Callbacks模块》。注意,tuples
的全部项中的 $Callbacks
都配置了 memory:1
,即开启记忆模式,增长的方法都会当即触发。包含 resove
和 reject
的项都传递了 once:1
,即回调列表只能触发一次。
stateString
是状态描述,只有包含了 resolve
和 reject
的数组项才具备。
index
为 1
的项,取出来的分别为 done
、 fail
和 progress
,因此 promise
上的 done
、 fail
和 progress
方法,调用的是 Callbacks
中的 add
方法,实质是往各自的回调列表中添加回调函数。
if (stateString) {
list.add(function(){
state = stateString
}, tuples[i^1][2].disable, tuples[2][2].lock)
}复制代码
若是 stateString
存在,即包含 resolve
和 reject
的数组项,则往对应的回调列表中添加切换 state
状态的方法,将 state
更改成对应方法触发后的状态。
同时,将状态锁定,即状态变为 resolved
或 rejected
状态后,不能再更改成其余状态。这里用来按位异或运算符 ^
来实现。当 i
为 0
,即状态变为 resolved
时, i^1
为 1
。tuples[i^1][2].disable
将 rejected
的回调列表禁用,当 i
为 1
时, i^1
为 0
,将 resolved
的回调列表禁用。即实现了成功和失败的状态互斥,作得状态锁定,不能再更改。
在状态变动后,同时将 tuples[2]
的回调列表锁定,要注意 disable
和 lock
的区别,具体见《读Zepto源码之Callbacks模块》,因为这里用了记忆模式,因此还能够往回调列表中添加回调方法,而且回调方法会当即触发。
deferred[tuple[0] + "With"] = list.fireWith
deferred[tuple[0]] = function(){
deferred[tuple[0] + "With"](this === deferred ? promise : this, arguments)
return this
}复制代码
这几个方法,存入在 deferred
对象中,并无存入 promise
对象。
resolveWith
、 rejectWith
和 notifyWith
方法,其实等价于 Callback
的 fireWith
方法,fireWith
方法的第一个参数为上下文对象。
从源码中能够看到 resolve
、reject
和 notify
方法,调用的是对应的 With
后缀方法,若是当前上下文为 deferred
对象,则传入 promise
对象做为上下文。
state: function() {
return state
},复制代码
state
方法的做用是返回当前的状态。
always: function() {
deferred.done(arguments).fail(arguments)
return this
},复制代码
always
是一种省事的写法,即不管成功仍是失败,都会执行回调。调用的是 deferred
上的 done
和 fail
方法。或许你会有疑惑,done
和 fail
方法,上面的分析中,明明是 promise
的方法,为何 deferred
对象上也有这两个方法呢,这个下面会讲到。
promise: function(obj) {
return obj != null ? $.extend( obj, promise ) : promise
}复制代码
返回 promise
对象,若是 obj
有传递,则将 promise
上的方法扩展到 obj
上。
then: function(/* fnDone [, fnFailed [, fnProgress]] */) {
var fns = arguments
return Deferred(function(defer){
$.each(tuples, function(i, tuple){
var fn = $.isFunction(fns[i]) && fns[i]
deferred[tuple[1]](function(){
var returned = fn && fn.apply(this, arguments)
if (returned && $.isFunction(returned.promise)) {
returned.promise()
.done(defer.resolve)
.fail(defer.reject)
.progress(defer.notify)
} else {
var context = this === promise ? defer.promise() : this,
values = fn ? [returned] : arguments
defer[tuple[0] + "With"](context, values)
}
})
})
fns = null
}).promise()
}复制代码
promise
的 then
方法接收三个参数,分别为成功的回调、失败的回调和进度的回调。
将 then
简化后,能够看到如下的结构:
return Deferred(function(defer){}).promise()复制代码
返回的是 deferred
对象,deferred
对象上的 promise
方法,其实就是 promise
对象上的 promise
方法,因此 then
方法,最终返回的仍是 promise
对象。因此 promise
能够这样一直调用下去 promise().then().then()....
。
var fns = arguments
return Deferred(function(defer) {
...
})
fns = null复制代码
这里的变量 fns
是 then
所传入的参数,即上文提到的三个回调。
最后的 fns = null
,是释放引用,让 JS
引擎能够进行垃圾回收。
Deferred
的参数是一个函数,上文在分析整体结构的时候,有一句关键的代码 if (func) func.call(deferred, deferred)
。因此这里的函数的参数 defer
即为 deferred
对象。
$.each(tuples, function(i, tuple){
var fn = $.isFunction(fns[i]) && fns[i]
deferred[tuple[1]](function(){
var returned = fn && fn.apply(this, arguments)
if (returned && $.isFunction(returned.promise)) {
returned.promise()
.done(defer.resolve)
.fail(defer.reject)
.progress(defer.notify)
} else {
var context = this === promise ? defer.promise() : this,
values = fn ? [returned] : arguments
defer[tuple[0] + "With"](context, values)
}
})
})复制代码
遍历 tuples
, tuples
中的顺序,跟 then
中规定 done
、fail
和 progress
的回调顺序一致。
因此用 var fn = $.isFunction(fns[i]) && fns[i]
来判断对应位置的参数是否为 function
类型,若是是,则赋值给 fn
。
deferred[tuple[1]]
是对应的是 done
、fail
和 progress
。因此在 then
里,会依次执行这三个方法。
var returned = fn && fn.apply(this, arguments)复制代码
returned
是 then
中三个回调方法执行后返回的结果。
if (returned && $.isFunction(returned.promise)) {
returned.promise()
.done(defer.resolve)
.fail(defer.reject)
.progress(defer.notify)
}复制代码
若是回调返回的是 promise
对象,调用新 promise
对象中的 promise
方法,新 promise
对象切换状态时, 并将当前 deferred
对象对应的状态切换方法传入,在新 promise
切换状态时执行。这就实现了两个 promise
对象的状态交流。
var context = this === promise ? defer.promise() : this,
values = fn ? [returned] : arguments
defer[tuple[0] + "With"](context, values)复制代码
若是返回的不是 promise
对象,则判断 this
是否为 promise
,若是是,则返回 defer.promise()
,修正执行的上下文。
而后调用对应的状态切换方法切换状态。
promise.promise(deferred)复制代码
从上面的分析中,能够看到,deferred
对象上并无done
、 fail
和 progress
方法,这是从 promise
上扩展来的。
既然已经有了一个拥有 promise
对象的全部方法的 deferred
对象,为何还要一个额外的 promise
对象呢?
promise
对象上没有状态切换方法,因此在 then
中,要绑定上下文的时候时候,绑定的都是 promise
对象,这是为了不在执行的过程当中,将执行状态改变。
$.when = function(sub) {
var resolveValues = slice.call(arguments),
len = resolveValues.length,
i = 0,
remain = len !== 1 || (sub && $.isFunction(sub.promise)) ? len : 0,
deferred = remain === 1 ? sub : Deferred(),
progressValues, progressContexts, resolveContexts,
updateFn = function(i, ctx, val){
return function(value){
ctx[i] = this
val[i] = arguments.length > 1 ? slice.call(arguments) : value
if (val === progressValues) {
deferred.notifyWith(ctx, val)
} else if (!(--remain)) {
deferred.resolveWith(ctx, val)
}
}
}
if (len > 1) {
progressValues = new Array(len)
progressContexts = new Array(len)
resolveContexts = new Array(len)
for ( ; i < len; ++i ) {
if (resolveValues[i] && $.isFunction(resolveValues[i].promise)) {
resolveValues[i].promise()
.done(updateFn(i, resolveContexts, resolveValues))
.fail(deferred.reject)
.progress(updateFn(i, progressContexts, progressValues))
} else {
--remain
}
}
}
if (!remain) deferred.resolveWith(resolveContexts, resolveValues)
return deferred.promise()
}复制代码
when
方法用来管理一系列的异步队列,若是全部的异步队列都执行成功,则执行成功方法,若是有一个异步执行失败,则执行失败方法。这个方法也能够传入非异步方法。
var resolveValues = slice.call(arguments),
len = resolveValues.length,
i = 0,
remain = len !== 1 || (sub && $.isFunction(sub.promise)) ? len : 0,
deferred = remain === 1 ? sub : Deferred(),
progressValues, progressContexts, resolveContexts,复制代码
slice
转换成数组形式。remain
初始化为 0
。其余状况,初始化为当前的个数。deferred
对象,若是只有一个异步对象(只有一个参数,而且不为异步对象时, remain
为 0
),则直接使用当前的 deferred
对象,不然建立一个新的 deferred
对象。updateFn = function(i, ctx, val){
return function(value){
ctx[i] = this
val[i] = arguments.length > 1 ? slice.call(arguments) : value
if (val === progressValues) {
deferred.notifyWith(ctx, val)
} else if (!(--remain)) {
deferred.resolveWith(ctx, val)
}
}
}复制代码
updateFn
方法,在每一个异步对象执行 resolve
方法和 progress
方法时都调用。
参数 i
为异步对象的索引值,参数 ctx
为对应的上下文数组,即 resolveContexts
或 resolveContexts
, val
为对应的回调函数数组,即 progresValues
或 resolveValues
。
if (val === progressValues) {
deferred.notifyWith(ctx, val)
}复制代码
若是为 progress
的回调,则调用 deferred
的 notifyWith
方法。
else if (!(--remain)) {
deferred.resolveWith(ctx, val)
}复制代码
不然,将 remain
减小 1
,若是回调已经执行完毕,则调用 deferred
的 resolveWith
方法。
if (len > 1) {
progressValues = new Array(len)
progressContexts = new Array(len)
resolveContexts = new Array(len)
for ( ; i < len; ++i ) {
if (resolveValues[i] && $.isFunction(resolveValues[i].promise)) {
resolveValues[i].promise()
.done(updateFn(i, resolveContexts, resolveValues))
.fail(deferred.reject)
.progress(updateFn(i, progressContexts, progressValues))
} else {
--remain
}
}
}复制代码
首先初始化 progressValues
、progressContexts
和 resolveContexts
,数组长度为异步对象的长度。
if (resolveValues[i] && $.isFunction(resolveValues[i].promise)) {
resolveValues[i].promise()
.done(updateFn(i, resolveContexts, resolveValues))
.fail(deferred.reject)
.progress(updateFn(i, progressContexts, progressValues))
}复制代码
若是为 promise
对象,则调用对应的 promise
方法。
else {
--remain
}复制代码
若是不是 promise
对象,则将 remian
减小 1
。
if (!remain) deferred.resolveWith(resolveContexts, resolveValues)
return deferred.promise()复制代码
若是无参数,或者参数不是异步对象,或者全部的参数列表都不是异步对象,则直接调用 resoveWith
方法,调用成功函数列表。
最后返回的是 promise
对象。
最后,全部文章都会同步发送到微信公众号上,欢迎关注,欢迎提意见:
做者:对角另外一面