Deferred延迟主要实现对异步的统一管理。本片文章主要较介绍Deferred对象的使用。
一、简化结构
jQuery.extend({
Deferred: function(){},
when: function(){}
})
// 对外提供的接口是
$.Deferred()
$.when()
二、用法
1、Deferred对象的使用方式类似于Callbacks对象的使用,因为Deferred封装和调用Callbacks对象来时先功能的。首先看下面的一组代码:
这两段代码运行的结果都是一样的,经过1秒后,先弹出111, 再弹出222.
可以简单的认为 resolve方法就是Callbacks对象中的fire方法。done方法就是Callbacks对象中的add方法。
2、其实,Deferred对象有多种状态,也就有相对应的多种回调函数。具体如下代码所示:
通过这组代码,我们可以知道,notify、resolve和reject都调用的Callbacks对象的add方法,progress、done、fail都调用的是Callbacks对象的fire方法。
3、可以使用延迟对象Deferred对jQuery.ajax()进行改造。
//传统的ajax的写法
$.ajax({
url:'xxx.php',
type: 'get',
success:function(){
alert('success')
},
error:function(){
alert('fail')
}
})
// 改造后的写法
$.ajax('xxx.php').done(function(){ alert('成功!') }).fail({ alert('失败') })
二、源码分析
1、定义方法调用元组
var tuples = [
// action, add listener, listener list, final state
[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
[ "notify", "progress", jQuery.Callbacks("memory") ]
]
1.1、首先,定义了一个数组tuples,保存状态和方法以及他们之间的对照关系。数组中每一条数据的参数的意思反别为:促成某种状态的方法(action)、执行方法(listener)、Callbacks回调对象(list)和 状态(state)。
1.2、至于为什么数组的前两项的Callbacks对象传入的参数为“once memory”,而第三项传入的参数为"memory",请看下面的示例代码:
这段代码运行的结果为 111,success,111,111,...,虽然使用了setInterval间歇调用函数,但是当调用了dfd.resolve()方法后,延迟对象dfd的状态已经被锁定为成功,所以第二次将不会在调用,这就要传入上面问题中传入“once”的缘由,保证只调用一次。当然调用dfd.reject() 和dfd.fail()方法也是一样的。
var dfd = $.Deferred()
setInterval(function(){
alert(111)
dfd.resolve()
},1000)
dfd.done(function(){
alert('success')
})
var dfd = $.Deferred()
setInterval(function(){
alert(111)
dfd.reject()
},1000)
dfd.fail(function(){
alert('success')
})
但是使用dfd.notify()和progress()方法方法时,会一直弹出,这当然是Callbacks对象中没有传递参数“once”的结果,示例代码如下:
var dfd = $.Deferred()
setInterval(function(){
alert(111)
dfd.notify()
},1000)
dfd.progress(function(){
alert('运行中')
})
3、Callbacks中传入“memory”这个参数,可以实现”记忆功能“,因为当传递了参数memory,那么自第一次调用fire()方法后,下次调用add方法就会自动调用fireWith方法,对使用add()方法添加进去的回调函数都会实现调用。具体请看下面示例:
var cb = $.Callbacks('memory')
cb.add(function(){
alert('aaa')
})
setTimeout(function(){
cb.fire()
},1000)
$('input').click(function(){
cb.add(function(){
alert('bbb')
})
})
var dfd = $.Deferred()
setTimeout(function(){
alert(111)
dfd.resolve()
},1000)
$('input').click(function(){
dfd.done(function(){
alert('bbb')
})
})
2、对应状态和方法
下面截取的代码片段中,会把添加到promise对象上,
promise[ done | fail | progress ] = list.add,
把添加到deferred对象上,
deferred[ resolve | reject | notify ]
// line 3095
jQuery.each( tuples, function( i, tuple ) {
// jQuery.Callbacks('once memory')
var list = tuple[ 2 ],
// promise[ done | fail | progress ] = list.add
// 这段代码表示promise.done, promise.fail, promise.progress调用的都是Callbacks对象实例的add()方法
promise[ tuple[1] ] = list.add;
// deferred[ resolve | reject | notify ]
deferred[ tuple[0] ] = function() {
deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
return this;
};
// 这段代码表示deferred[resolve|reject|notify] 调用的都是Callbacks对象实例的fireWith() 方法
deferred[ tuple[0] + "With" ] = list.fireWith;
3、简化promise函数的结构
3.1 3053-3088行代码中,定义了promise对象,可以将其代码简化建构如下所示:
用色标注的方法是前面each遍历之后的为promise和deferred两个对象新增的方法。
3.2 然后通过 promise.promise( deferred )将promise对象的上方法全部拷贝到deferred对象上面。我们先看一下promise.promise这个方法:
promise: function( obj ) {
return obj != null ? jQuery.extend( obj, promise ) : promise;
}
此处要主要,当不给promise传递参数,那么执行这个方法就会返回promise对象。具体的示例,请看下面代码:
function aaa(){
var dfd = $.Deferred()
setTimeout(function(){
dfd.resolve()
}, 1000)
return dfd
}
var newDerred = aaa()
newDerred.done(function(){
alert('success')
}).fail(function(){
alert('fail')
})
newDerred.reject()
运行上面的代码,因为有1000ms的延迟,所以可以在外部通过newDerred.reject()来改变Deferred的状态,所以会返回“fail”。如果将aaa函数中的return dfd换成 return dfd.promise(),那么就不会被外边的newDerred.reject()改变状态了。因为在调用dfd.promise()方法没有传参,所以会返回promise对象,而promise对象中并没有resolve,reject,notify等状态方法,所以代码除了运行正确还会有报错信息,Uncaught TypeError:newDerred.reject is not a function
4、promise各方法的使用
下面我们来看一下promise个方法的使用。
4.1 state() 获取Deferred对象的状态
var dfd = $.Deferred()
alert(dfd.state())
setTimeout(function(){
dfd.resolve()
alert(dfd.state())
},1000)
dfd.done(function(){
alert('success')
}).fail(function(){
alert('fail')
})
上面的代码会首先弹出pending,然后1000ms后紧接着弹出success和resolved,关于Dererred对象状态更改的源码如下:
jQuery.each( tuples, function( i, tuple ) {
...
var stateString = tuple[ 3 ]
if ( stateString ) {
list.add(function() {
// state = [ resolved | rejected ]
state = stateString;
// [ reject_list | resolve_list ].disable; progress_list.lock
}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
}
这段源码表明,在遍历tuples时,通过add方法将修改状态的回调函数添加进各自的Callbacks对象中,同时当状态确定之后(从penging转化为resolve,)
4.2、always() 不管成功还是失败,都会调用这个方法添加的回调
var dfd = $.Deferred()
setTimeout(function(){
dfd.resolve()
},1000)
dfd.done(function(){
alert('success')
}).fail(function(){
alert('fail')
}).always(function(){
alert('always')
})
上面的代码执行后会先后弹出“success” 、“always”,如果使用dfd.reject()则相应的会先后弹出“fail”、“always”。其源码如下:
always: function() {
deferred.done( arguments ).fail( arguments );
return this;
}
这段源码表明,如果不执行正确的回调方法done、就执行错误的回调方法fail。总之,不会调用resolve()还是调用reject(),这个always()方法都可以起作用。
4.3、then(fnDone, fnFail, fnProgress)
除了上面的链式的调用,还可以以then的方式进行调用,具体如下所示:
var dfd = $.Deferred()
setTimeout(function(){
dfd.resolve()
},1000)
dfd.then(function(){
alert('success')
},function(){
alert('fail')
},function(){
alert('进行中...')
})
源码如下:
then: function( /* fnDone, fnFail, fnProgress */ ) {
var fns = arguments;
return jQuery.Deferred(function( newDefer ) {
jQuery.each( tuples, function( i, tuple ) {
var action = tuple[ 0 ],
fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
// deferred[ done | fail | progress ] for forwarding actions to newDefer
deferred[ tuple[1] ](function() {
var returned = fn && fn.apply( this, arguments );
if ( returned && jQuery.isFunction( returned.promise ) ) {
returned.promise()
.done( newDefer.resolve )
.fail( newDefer.reject )
.progress( newDefer.notify );
} else {
newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
}
});
});
fns = null;
}).promise();
}
这段源码最核心的是下面的代码,也是我所不能理解的。
deferred[ tuple[1] ](function() {
var returned = fn && fn.apply( this, arguments );
if ( returned && jQuery.isFunction( returned.promise ) ) {
returned.promise()
.done( newDefer.resolve )
.fail( newDefer.reject )
.progress( newDefer.notify );
} else {
newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
}
});
这个if循环是pipe()方法的代码。对于then()方法,我们主要关心的是var returned = fn && fn.apply( this, arguments )这段代码。按照前面的分析,deferred[ tuple[1] ]应该是deferred['done|fail|progress'],也就是执行Callbacks对象中的add()操作,但是 fn.apply( this, arguments )中this 和argements的传值我并不是很清楚。下面是两段示例代码:
var dfd = $.Deferred()
setTimeout(function(){
dfd.resolve('aaa')
},1000)
dfd.then(function(){
alert(arguments[0]) //aaa
},function(){
alert('fail')
},function(){
alert('进行中...')
})
按照这样上面的arguments对象是应该是dfd.resolve()这个地方传递进去的‘aaa',然后在后面的then()方法调用中被接收。
至于pipe方法,请看下面的示例:
var dfd = $.Deferred()
setTimeout(function(){
dfd.resolve('aaa')
},1000)
var newDeferred = dfd.pipe(function(){
return arguments[0] + '妙味'
})
newDeferred.done(function(){
alert(arguments[0]) // aaa妙味
})
通过使用pipe管道方法,我们会得到一个新Deferred对象,并对其中的参数进行加工,这个尚且不能很好的理解。