深入解析jQuery中Deferred的deferred.promise()方法
deferred.promise()和.promise()
这两个API语法几乎一样,但是有着很大的差别。deferred.promise()是Deferred实例的一个方法,他返回一个Deferred.Promise实例。一个Deferred.Promise对象可以理解为是deferred对象的一个视图,它只包含deferred对象的一组方法,包括:done(),then(),fail(),isResolved(),isRejected(),always(),这些方法只能观察一个deferred的状态,而无法更改deferred对象的内在状态。这非常适合于API的封装。例如一个deferred对象的持有者可以根据自己的需要控制deferred状态的状态(resolved或者rejected),但是可以把这个deferred对象的Promise对象返回给其它的观察者,观察者只能观察状态的变化绑定相应的回调函数,但是无法更改deferred对象的内在状态,从而起到很好的隔离保护作用。
deferred.promise()
$(function(){
//
vardeferred=$.Deferred();
varpromise=deferred.promise();
vardoSomething=function(promise){
promise.done(function(){
alert('deferredresolved.');
});
};
deferred.resolve();
doSomething(promise);
})
deferred.promise()也可以接受一个object参数,此时传入的object将被赋予Promise的方法,并作为结果返回。
//Existingobject
varobj={
hello:function(name){
alert("Hello"+name);
}
},
//CreateaDeferred
defer=$.Deferred();
//Setobjectasapromise
defer.promise(obj);
//Resolvethedeferred
defer.resolve("John");
//UsetheobjectasaPromise
obj.done(function(name){
this.hello(name);//willalert"HelloJohn"
}).hello("Karl");//willalert"HelloKarl"
deferred.promise()只是阻止其他代码来改变这个deferred对象的状态。可以理解成,通过deferred.promise()方法返回的deferredpromise对象,是没有resolve,reject,progress,resolveWith,rejectWith,progressWith这些可以改变状态的方法,你只能使用done,then,fail等方法添加handler或者判断状态。
deferred.promise()改变不了deferred对象的状态,作用也不是保证目前的状态不变,它只是保证你不能通过deferred.promise()返回的deferredpromise对象改变deferred对象的状态。如果我们这个地方直接返回dtd,也是可以工作的,.done的处理函数还是会等到dtd.resolve()之后才会执行.
具体在那篇博客的例子,如果我们把代码改成如下的形式:
vardtd=$.Deferred();//新建一个deferred对象
varwait=function(dtd){
vartasks=function(){
alert("执行完毕!");
dtd.resolve();//改变deferred对象的执行状态
};
setTimeout(tasks,5000);
returndtd;
};
$.when(wait(dtd))
.done(function(){alert("哈哈,成功了!");})
.fail(function(){alert("出错啦!");});
这样的执行结果和先前返回dtd.promise的结果是一样的。
差别在什么地方呢?如果我们把$.when的这块的代码改成这样的:
vard=wait(dtd);
$.when(d)
.done(function(){alert("哈哈,成功了!");})
.fail(function(){alert("出错啦!");});
d.resolve();
我们会发现alert(“哈哈,成功了!”)会立即执行,“执行完毕”却需要5秒后才弹出来。
但是如果我们wait函数最后是returndtd.promise()这里d.resolve()就会报错了,因为对象d不存在resolve()方法。
同样如果我们把代码改成:
vardtd=$.Deferred();//新建一个deferred对象
varwait=function(dtd){
vartasks=function(){
alert("执行完毕!");
dtd.resolve();//改变deferred对象的执行状态
};
setTimeout(tasks,5000);
returndtd.promise();
};
dtd.resolve();
$.when(wait(dtd))
.done(function(){alert("哈哈,成功了!");})
.fail(function(){alert("出错啦!");});
我们也可以发现alert(“哈哈,成功了!”)会立即执行,因为dtd这个deferred对象在被传入wait之前,已经被resolve()了,而deferred对象一旦被resolve或者reject之后,状态是不会改变的。
然后我们再把$.wait这块的代码改成:
$.when(wait(dtd))
.done(function(){alert("哈哈,成功了!");})
.fail(function(){alert("出错啦!");});
dtd.resolve();
我们也会发现alert(“哈哈,成功了!”);被立即执行,虽然wait(dtd)执行的时候,dtd还没有被resolve,而且wait方法返回的是dtd.promise(),但是dtd这个原始的deferred对象是暴露在外面的,我们还是可以从外面改变它的状态。
于是,如果我们真的不想让其他代码能改变wait方法内部的deferred对象的状态,那我们应该写成这样:
varwait=function(){
vardtd=$.Deferred();//新建一个deferred对象
vartasks=function(){
alert("执行完毕!");
dtd.resolve();//改变deferred对象的执行状态
};
setTimeout(tasks,5000);
returndtd.promise();
};
$.when(wait())
.done(function(){alert("哈哈,成功了!");})
.fail(function(){alert("出错啦!");});
也就是不要把deferred直接暴露出来,最后返回deferred.promise(),让其他地方的代码只能添加handler。
.promise()
首先这不是Deferred实例的方法!该方法是jQuery实例的方法。该方法用于一组类型的动作(例如动画)全部完成后返回一个Promise对象,供事件监听器监听其状态并执行相应的处理函数。
该方法接受两个可选参数:.promise([type,][target])
type:队列的类型,默认值是fx,fx即jQuery对象的动画.
targetObject:要赋予Promise行为的对象,
这两个参数是可选的。其中第一个参数(我)目前除了fx还没有找到其他的值类型。因此一般都是用于动画的监控,在动画完成后做一些操作。
例子:没有动画效果直接返回一个resolved状态的promise对象
vardiv=$("<div/>");
div.promise().done(function(arg1){
//将会被马上触发
alert(this===div&&arg1===div);
});
例子:在动画效果全部完成后触发done()监听函数
<!DOCTYPEhtml>
<html>
<head>
<style>
div{
height:50px;width:50px;
float:left;margin-right:10px;
display:none;background-color:#090;
}
</style>
<scriptsrc="http://code.jquery.com/jquery-latest.js"></script>
</head>
<body>
<button>Go</button>
<p>Ready...</p>
<div></div>
<div></div>
<div></div>
<div></div>
<script>
$("button").bind("click",function(){
$("p").append("Started...");
//每个div执行动画效果
$("div").each(function(i){
$(this).fadeIn().fadeOut(1000*(i+1));
});
//$("div")包含一组div,在所有的div都完成自己的动画效果后触发done()函数
$("div").promise().done(function(){
$("p").append("Finished!");
});
});
</script>
</body>
</html>