转自:“阮一峰”:http://www.ruanyifeng.com/blo…
一、什么是deferred对象?
开辟网站的过程当中,我们常常碰到某些耗时很长的javascript操纵。个中,既有异步的操纵(比方ajax读取服务器数据),也有同步的操纵(比方遍历一个大型数组),它们都不是马上能获得效果的。
一般的做法是,为它们指定回调函数(callback)。即事前划定,一旦它们运转终了,应当挪用哪些函数。
然则,在回调函数方面,jQuery的功用异常弱。为了转变这一点,jQuery开辟团队就设想了deferred对象。
简单说,deferred对象就是jQuery的回调函数处置惩罚方案。在英语中,defer的意义是”耽误”,所以deferred对象的寄义就是”耽误”到将来某个点再实行。
它处置惩罚了如何处置惩罚耗时操纵的题目,对那些操纵供应了更好的掌握,以及一致的编程接口。它的主要功用,能够归结为四点。下面我们经由过程示例代码,一步步来进修。
二、ajax操纵的链式写法
起首,回忆一下jQuery的ajax操纵的传统写法:
$.ajax({
url: "test.html",
success: function(){
alert("哈哈,胜利了!");
},
error:function(){
alert("失足啦!");
}
});
在上面的代码中,$.ajax()接收一个对象参数,这个对象包括两个要领:success要领指定操纵胜利后的回调函数,error要领指定操纵失利后的回调函数。
$.ajax()操纵完成后,假如运用的是低于1.5.0版本的jQuery,返回的是XHR对象,你没法举行链式操纵;假如高于1.5.0版本,返回的是deferred对象,能够举行链式操纵。
如今,新的写法是如许的:
$.ajax("test.html")
.done(function(){ alert("哈哈,胜利了!"); })
.fail(function(){ alert("失足啦!"); });
能够看到,done()相当于success要领,fail()相当于error要领。采纳链式写法今后,代码的可读性大大提高。
三、指定统一操纵的多个回调函数
deferred对象的一大长处,就是它许可你自在增加多个回调函数。
照样以上面的代码为例,假如ajax操纵胜利后,除了本来的回调函数,我还想再运转一个回调函数,怎么办?
很简单,直接把它加在背面就好了。
$.ajax("test.html")
.done(function(){ alert("哈哈,胜利了!");} )
.fail(function(){ alert("失足啦!"); } )
.done(function(){ alert("第二个回调函数!");} );
回调函数能够增加恣意多个,它们依据增加递次实行。
四、为多个操纵指定回调函数
deferred对象的另一大长处,就是它许可你为多个事宜指定一个回调函数,这是传统写法做不到的。
请看下面的代码,它用到了一个新的要领$.when():
$.when($.ajax("test1.html"), $.ajax("test2.html"))
.done(function(){ alert("哈哈,胜利了!"); })
.fail(function(){ alert("失足啦!"); });
这段代码的意义是,先实行两个操纵$.ajax(“test1.html”)和$.ajax(“test2.html”),假如都胜利了,就运转done()指定的回调函数;假如有一个失利或都失利了,就实行fail()指定的回调函数。
五、一般操纵的回调函数接口(上)
deferred对象的最大长处,就是它把这一套回调函数接口,从ajax操纵扩大到了一切操纵。也就是说,任何一个操纵—-不论是ajax操纵照样当地操纵,也不论是异步操纵照样同步操纵—-都能够运用deferred对象的种种要领,指定回调函数。
我们来看一个详细的例子。假定有一个很耗时的操纵wait:
var wait = function(){
var tasks = function(){
alert("实行终了!");
};
setTimeout(tasks,5000);
};
我们为它指定回调函数,应当怎么做呢?
很天然的,你会想到,能够运用$.when()
$.when(wait())
.done(function(){ alert("哈哈,胜利了!"); })
.fail(function(){ alert("失足啦!"); });
然则,如许写的话,done()要领会马上实行,起不到回调函数的作用。缘由在于$.when()的参数只能是deferred对象,所以必需对wait()举行改写:
var dtd = $.Deferred(); // 新建一个deferred对象
var wait = function(dtd){
var tasks = function(){
alert("实行终了!");
dtd.resolve(); // 转变deferred对象的实行状况
};
setTimeout(tasks,5000);
return dtd;
};
如今,wait()函数返回的是deferred对象,这就能够加上链式操纵了。
$.when(wait(dtd))
.done(function(){ alert("哈哈,胜利了!"); })
.fail(function(){ alert("失足啦!"); });
wait()函数运转完,就会自动运转done()要领指定的回调函数。
六、deferred.resolve()要领和deferred.reject()要领
假如仔细看,你会发如今上面的wait()函数中,另有一个处所我没解说。那就是dtd.resolve()的作用是什么?
要说清晰这个题目,就要引入一个新概念”实行状况“。jQuery划定,deferred对象有三种实行状况—-未完成,已完成和已失利。假如实行状况是”已完成”(resolved),deferred对象马上挪用done()要领指定的回调函数;假如实行状况是”已失利”,挪用fail()要领指定的回调函数;假如实行状况是”未完成”,则继承守候,或许挪用progress()要领指定的回调函数(jQuery1.7版本增加)。
前面部份的ajax操纵时,deferred对象会依据返回效果,自动转变本身的实行状况;然则,在wait()函数中,这个实行状况必需由程序员手动指定。dtd.resolve()的意义是,将dtd对象的实行状况从”未完成”改成”已完成”,从而触发done()要领。
相似的,还存在一个deferred.reject()要领,作用是将dtd对象的实行状况从”未完成”改成”已失利”,从而触发fail()要领。
var dtd = $.Deferred(); // 新建一个Deferred对象
var wait = function(dtd){
var tasks = function(){
alert("实行终了!");
dtd.reject(); // 转变Deferred对象的实行状况
};
setTimeout(tasks,5000);
return dtd;
};
$.when(wait(dtd))
.done(function(){ alert("哈哈,胜利了!"); })
.fail(function(){ alert("失足啦!"); });
七、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对象上返回另一个deferred对象,后者只开放与转变实行状况无关的要领(比方done()要领和fail()要领),屏障与转变实行状况有关的要领(比方resolve()要领和reject()要领),从而使得实行状况不能被转变。
请看下面的代码:
var dtd = $.Deferred(); // 新建一个Deferred对象
var wait = function(dtd){
var tasks = function(){
alert("实行终了!");
dtd.resolve(); // 转变Deferred对象的实行状况
};
setTimeout(tasks,5000);
return dtd.promise(); // 返回promise对象
};
var d = wait(dtd); // 新建一个d对象,改成对这个对象举行操纵
$.when(d)
.done(function(){ alert("哈哈,胜利了!"); })
.fail(function(){ alert("失足啦!"); });
d.resolve(); // 此时,这个语句是无效的
在上面的这段代码中,wait()函数返回的是promise对象。然后,我们把回调函数绑定在这个对象上面,而不是本来的deferred对象上面。如许的长处是,没法转变这个对象的实行状况,要想转变实行状况,只能操纵本来的deferred对象。
不过,更好的写法是allenm所指出的,将dtd对象变成wait()函数的内部对象。
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()。
这时候,wait函数照样坚持稳定,我们直接把它传入$.Deferred():
$.Deferred(wait)
.done(function(){ alert("哈哈,胜利了!"); })
.fail(function(){ alert("失足啦!"); });
jQuery划定,$.Deferred()能够接收一个函数名(注重,是函数名)作为参数,$.Deferred()所天生的deferred对象将作为这个函数的默许参数
九、一般操纵的回调函数接口(下)
除了上面两种要领之外,我们还能够直接在wait对象上布置deferred接口。
var dtd = $.Deferred(); // 天生Deferred对象
var wait = function(dtd){
var tasks = function(){
alert("实行终了!");
dtd.resolve(); // 转变Deferred对象的实行状况
};
setTimeout(tasks,5000);
};
dtd.promise(wait);
wait.done(function(){ alert("哈哈,胜利了!"); })
.fail(function(){ alert("失足啦!"); });
wait(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("已实行!");} );