今天介绍jQuery1.5.0版本引入的一个新功能--Deferred.
一,什么是Deferred对象?
开发网站中经常有一些耗时操作,这些耗时操作既有异步操作(ajax读取服务器数据),也有同步操作(遍历一些大型数组),通常这些操作不能马上得到结果,我们这时候就会用回调函数(callback),原来jQuery对回调函数方面的功能比较弱,于是有了Deferred。所以说Deferred是jQuery的异步回调解决方案。
二,ajax链式写法
jQuery的ajax操作的传统写法:
$.ajax({ url: "test.php", success: function(){ alert("哈哈,成功了!"); }, error:function(){ alert("出错啦!"); } });
在jQuery1.5.0以下版本$.ajax()返回的xhr对象,没有链式写法,jQuery1.5.0以上版本支持链式写法。
$.ajax('test.php').done(function() { alert('成功'); }).fail(function() { alert('失败'); });
可以看到,done()相当于success方法,fail()相当于error方法。采用链式写法以后,代码的可读性大大提高。
三、为同一操作指定多个回调函数
很简单,还是以上面的代码为例,直接把它加在后面就行了。
$.ajax('test.php').done(function() { alert('成功'); }).fail(function() { alert('失败'); }).done(function(){ alert('成功2'); });
四、为多个操作指定回调函数
deferred对象的另一大好处,就是它允许你为多个事件指定一个回调函数。
$.when($.ajax('test1.php'),$.ajax('test2.php')).done(function() { alert('成功'); }).fail(function() { alert('失败'); });
这段代码的意思是,如果$.ajax('test1.php')和$.ajax('test2.php')都成功了,就执行done()指定的回调函数;如果有一个失败或都失败了,就执行fail()指定的回调函数。 五、普通操作的回调函数接口(上)
deferred对象的最大优点,就是它把这一套回调函数接口,从ajax操作扩展到了所有操作。也就是说,任何一个操作----不管是ajax操作还是本地操作,也不管是异步操作还是同步操作----都可以使用deferred对象的各种方法,指定回调函数。
我们来看一个具体的例子。假定有一个很耗时的操作wait:
var wait = function() { var tasks = function() { alert('执行完毕'); } setTimeout(tasks, 3000); }
如何为它指定回调函数?
可以使用$.when():
$.when(wait()).done(function(){ alert('成功'); }).fail(function(){ alert('失败'); });
但是,这样写的话,done()方法会先立即执行,然后等待3s执行tasks函数,这并不是我们要的结果,会有这样结果的原因在于$.when()的参数只能是deferred对象,所以我们要对wait()进行改写:
var dtd = $.Deferred();//创建一个Deferred对象 var wait = function(dtd) { var tasks = function() { alert('执行完毕'); dtd.resolve();//改变Deferred对象执行状态 } setTimeout(tasks, 3000); return dtd; } $.when(wait(dtd)).done(function() { alert('成功'); }).fail(function() { alert('失败'); });
wait()函数运行完,就会自动运行done()方法指定的回调函数。
详解:deferred.resolve()方法和deferred.reject()方法
要理解这两个方法先了解deferred对象的三种执行状态: 1,pending:deferred执行状态未完成 2,resolved:deferred执行状态完成 3,rejected:deferred执行状态失败 执行deferred.resolve()方法,deferred执行状态从未完成变成已完成状态(即:resolved状态),done回调函数执行,执行deferred.reject()方法,deferred执行状态从未完成变成失败状态(即:rejected状态),fail回调函数执行。 六、deferred.promise()方法
上面这种写法,由于dtd是一个全局对象,所以它的执行状态可以从外部改变。
请看下面的代码:
var dtd = $.Deferred(); // 新建一个Deferred对象 var wait = function(dtd){ var tasks = function(){ alert("执行完毕!"); dtd.resolve(); // 改变Deferred对象的执行状态 }; setTimeout(tasks,5000); return dtd; }; $.when(wait(dtd)) .done(function(){ alert("哈哈,成功了!"); }) .fail(function(){ alert("出错啦!"); }); dtd.resolve();
在代码的尾部加了一行dtd.resolve(),这就改变了dtd对象的执行状态,因此导致done()方法立刻执行,跳出"哈哈,成功了!"的提示框,等5秒之后再跳出"执行完毕!"的提示框。
为了避免这种情况,jQuery提供了deferred.promise()方法。由deferred.promise()方法生成的deferred对象不能改变deferred的执行状态(即不支持deferred.resolve()方法和deferred.reject()方法),但是仍然可以执行done和fail回调函数. 示例:
var wait = function(dtd) { var dtd = $.Deferred(); //在函数内部,新建一个Deferred对象 var tasks = function() { alert("执行完毕!"); dtd.resolve(); // 改变Deferred对象的执行状态 }; setTimeout(tasks, 5000); return dtd.promise(); // 返回promise对象 }; $.when(wait()).done(function() { alert("哈哈,成功了!"); }).fail(function() { alert("出错啦!"); });
七、$.Deferred
另一种防止执行状态被外部改变的方法,是使用deferred对象的构建函数$.Deferred()。
这时,wait函数还是保持不变,我们直接把它传入$.Deferred():
var awit = function(dtd) { var tasks = function() { alert('执行完毕'); dtd.resolve(); } setTimeout(tasks, 3000); return dtd.promise(); } $.Deferred(awit).done(function() { alert('成功'); }).fail(function() { alert('失败'); });
jQuery规定,$.Deferred()可以接受一个函数名(注意,是函数名)作为参数,$.Deferred()所生成的deferred对象将作为这个函数的默认参数。
八、在wait对象上部署deferred接口。
var dtd = $.Deferred(); var awit = function(dtd) { var tasks = function() { alert('执行完毕'); dtd.resolve(); } setTimeout(tasks, 3000); } dtd.promise(awit); awit.done(function() { alert('成功'); }).fail(function() { alert('失败'); }).done(function() { alert('成功2'); }); awit(dtd);
这里的关键是dtd.promise(wait)这一行,它的作用就是在wait对象上部署Deferred接口。正是因为有了这一行,后面才能直接在wait上面调用done()和fail()。
十、小结:deferred对象的方法
前面已经讲到了deferred对象的多种方法,下面做一个总结:
(1) $.Deferred() 生成一个deferred对象。
(2) deferred.done() 指定操作成功时的回调函数
(3) deferred.fail() 指定操作失败时的回调函数
(4) deferred.promise() 没有参数时,返回一个新的deferred对象,该对象的运行状态无法被改变;接受参数时,作用为在参数对象上部署deferred接口。
(5) deferred.resolve() 手动改变deferred对象的运行状态为"已完成",从而立即触发done()方法。
(6)deferred.reject() 这个方法与deferred.resolve()正好相反,调用后将deferred对象的运行状态变为"已失败",从而立即触发fail()方法。
(7) $.when() 为多个操作指定回调函数。
除了这些方法以外,deferred对象还有二个重要方法,上面的教程中没有涉及到。
(8)deferred.then()
有时为了省事,可以把done()和fail()合在一起写,这就是then()方法。
$.when($.ajax( "/main.php" ))
.then(successFunc, failureFunc );
如果then()有两个参数,那么第一个参数是done()方法的回调函数,第二个参数是fail()方法的回调方法。如果then()只有一个参数,那么等同于done()。
(9)deferred.always()
这个方法也是用来指定回调函数的,它的作用是,不管调用的是deferred.resolve()还是deferred.reject(),最后总是执行。
$.ajax( "test.html" )
.always( function() { alert("已执行!");} );
参考文章:http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquery_deferred_object.html
作者:阮一峰