1.jQuery.Defered
1.1 什么是 deferred 對象
開發(fā)網(wǎng)站過程中,我們經(jīng)常遇到某些耗時很長的 JavaScript 操作。
其中,既有異步的操作(比如 ajax 讀取服務(wù)器數(shù)據(jù)),也有同步的操作(比如遍歷一個大型數(shù)組)腾供,它們都不是立即能得到結(jié)果的。
通常的做法是鲜滩,為它們指定回調(diào)函數(shù)伴鳖,即事先規(guī)定,一旦它們運(yùn)行結(jié)束徙硅,應(yīng)該調(diào)用哪些函數(shù)榜聂。
簡單說,defered 對象就是 jQuery 的回調(diào)函數(shù)解決方法闷游,它是 jQuery 1.5.0 版本開始引入的新功能峻汉。
1.2 ajax 的鏈?zhǔn)綄懛?/h6>
jQuery 1.5以下版本的 ajax 操作的傳統(tǒng)寫法如下:
$.ajax({
url:"test.html",
success:function(){
alert("成功了!");
},
error:function(){
alert("失敗了脐往!");
}
});
上面的代碼中休吠,$.ajax() 接受一個參數(shù)對象,這個對象包含兩個方法:success 方法指定操作成功后的回調(diào)函數(shù)业簿,error 方法指定操作失敗后的回調(diào)函數(shù)瘤礁。
jQuery 1.5以上版本,新的寫法如下:
$.ajax("test.html")
.done(function(){ alert("成功了梅尤!"); })
.fail(function(){ alert("失敗了柜思!");});
可以看到,done() 方法相當(dāng)于 success 方法巷燥,fail() 相當(dāng)于 error 方法赡盘。
1.3 指定同一操作的多個回調(diào)函數(shù)
deferred 對象的一大好處,就是它允許自由添加多個回調(diào)函數(shù)缰揪。直接把新的回調(diào)函數(shù)添加到后面就行了:
$.ajax("test.html")
.done(function(){ alert("成功了陨享!"); })
.fail(function(){ alert("失敗了!");})
.done(function(){ alert("這里是第二個回調(diào)函數(shù)!"); });
回調(diào)函數(shù)可以任意多個抛姑,它們按照添加順序執(zhí)行赞厕。
1.4 為多個操作指定回調(diào)函數(shù)
deferred 對象的另一好處,就是它允許你為多個事件指定一個回調(diào)函數(shù)定硝,這是傳統(tǒng)寫法做不到的皿桑。
它用到了一個新的方法 $.when():
$.when($.ajax("test1.html"),$.ajax("test.html"))
.done(function(){ alert("成功了!"); })
.fail(function(){ alert("失敗了蔬啡!"); });
這段代碼的意思是诲侮,先執(zhí)行兩個操作 $.ajax("test1.html") 和 $.ajax("test.html"),如果都成功了星爪,就運(yùn)行 done() 指定的回調(diào)函數(shù)浆西;如果有一個失敗了粉私,就執(zhí)行 fail() 回調(diào)函數(shù)顽腾。
1.5 普通操作的回調(diào)函數(shù)接口(上)
deferred 對象的最大優(yōu)點,就是它把這一套回調(diào)函數(shù)接口诺核,從 ajax 操作擴(kuò)展到了所有操作抄肖。也就是說,任何一個操作 —— 不管它是 ajax 還是本地操作窖杀,也不管是異步操作還是同步操作 —— 都可以使用 deferred 對象的各個方法漓摩,指定回調(diào)函數(shù)。
我們來看一個具體的例子入客。假定有一個很耗時的操作 wait:
var wait = function(){
var tasks = function(){
alert("執(zhí)行完畢管毙!");
};
setTimeout(tasks,5000);
}
很自然的,你會想到桌硫,可以使用 $.when():
$.when(wait())
.done(function(){ alert("成功了夭咬!"); })
.fail(function(){ alert("失敗了!"); });
但是铆隘,這樣寫的話卓舵,done() 方法會立即執(zhí)行,起不到回調(diào)函數(shù)的作用膀钠。原因在于 $.when() 的參數(shù)只能是 deferred 對象掏湾,所以必須對 wait() 進(jìn)行改寫:
var dtd = $.Deferred(); // 新建一個 deferred 對象
var wait = function(){
var tasks = function(){
alert("執(zhí)行完畢!");
dtd.resolve(); // 改變 deferred 對象的執(zhí)行狀態(tài)
}
setTimeout(tasks,5000);
return dtd;
}
現(xiàn)在肿嘲,wait() 返回的是 deferred 對象融击,這就可以加上鏈?zhǔn)讲僮髁恕ait() 函數(shù)運(yùn)行完雳窟,就會自動運(yùn)行 done() 方式指定的回調(diào)函數(shù)尊浪。
1.6 deferred.resolve() 方法和 deferred.reject() 方法
jQuery 規(guī)定,deferred 對象有三種執(zhí)行狀態(tài) —— 未完成,已完成和已失敗际长。如果執(zhí)行狀態(tài)是“已完成”(resolved)耸采,deferred 對象立即調(diào)用 done() 方法指定的回調(diào)函數(shù);如果執(zhí)行狀態(tài)是“已失敗”工育,調(diào)用 fail() 方法指定的回調(diào)函數(shù)虾宇;如果執(zhí)行狀態(tài)是“未完成”,則繼續(xù)等待如绸,或者調(diào)用 progress() 方法指定的回調(diào)函數(shù)(jQuery 1.7版本添加)嘱朽。
前面部分的 ajax 操作時,deferred 對象會根據(jù)返回結(jié)果怔接,自動改變自身的執(zhí)行結(jié)果但是搪泳,在 wait() 函數(shù)中,這個執(zhí)行狀態(tài)必須由程序員手動指定扼脐。 dtd.resolve() 的意思是岸军,將 dtd 對象的執(zhí)行狀態(tài)從“未完成”改為“已完成”,從而觸發(fā) done() 方法瓦侮。
類似的艰赞,還是存在一個 deferred.reject() 方法,作用是將 dtd 對象的執(zhí)行狀態(tài)從“未完成”改為“已失敗”肚吏,從而觸發(fā) fail() 方法方妖。
1.7 deferred.promise() 方法
上面這種寫法,還是有問題罚攀。那就是 dtd 是一個全局對象党觅,所以它的執(zhí)行狀態(tài)可以從外部改變。
var dtd = $.Deferred(); // 新建一個 deferred 對象
var wait = function(){
var tasks = function(){
alert("執(zhí)行完畢斋泄!");
dtd.resolve(); // 改變 deferred 對象的執(zhí)行狀態(tài)
}
setTimeout(tasks,5000);
return dtd;
}
$.when(wait())
.done(function(){ alert("成功了杯瞻!"); })
.fail(function(){ alert("失敗了!"); });
dtd.resolve();
上面的代碼尾部加了一行 dtd.resolve() 是己,這就改變了 dtd對象的執(zhí)行狀態(tài)又兵,因此導(dǎo)致 done() 方法立即執(zhí)行,跳出“成功了卒废!”的提示框沛厨,等5秒之后再跳出“執(zhí)行完畢!”的提示框摔认。
為了避免這種情況逆皮,jQuery 提供了 deferred.promise() 方法。它的作用是参袱,在原來的 deferred 對象上返回另一個 deferred 對象电谣,后者只開放與改變執(zhí)行狀態(tài)無關(guān)的方法(比如 done() 方法和 fail() 方法)秽梅,屏蔽與改變執(zhí)行狀態(tài)有關(guān)的方法(比如 resolve() 方法和 reject() 方法),從而使得執(zhí)行狀態(tài)不能改變剿牺。
請看下面代碼:
var dtd = $.Deferred(); // 新建一個 deferred 對象
var wait = function(){
var tasks = function(){
alert("執(zhí)行完畢企垦!");
dtd.resolve(); // 改變 deferred 對象的執(zhí)行狀態(tài)
}
setTimeout(tasks,2000);
return dtd.promise(); // 返回 promise 對象
}
var d = wait(); // 新建一個d對象,改為對這個對象進(jìn)行操作
$.when(d)
.done(function(){ alert("成功了晒来!"); })
.fail(function(){ alert("失敗了钞诡!"); });
d.resolve(); //此時,這個語句是無效的
在上面的這段代碼中湃崩,wait() 函數(shù)返回的是 promise 對象荧降。然后,我們把回調(diào)函數(shù)綁定在這個對象上面攒读,而不是原來的 deferred 對象上面朵诫。這樣的好處是,無法改變這個對象的執(zhí)行狀態(tài)薄扁,要想改變執(zhí)行狀態(tài)剪返,只能操作原來的 deferred 對象。
不過泌辫,更好的寫法是將 dtd 對象變成 wait() 函數(shù)的內(nèi)部對象随夸。
var wait = function(){
var dtd = $.Deferred(); // 新建一個 deferred 對象
var tasks = function(){
alert("執(zhí)行完畢!");
dtd.resolve(); // 改變 deferred 對象的執(zhí)行狀態(tài)
}
setTimeout(tasks,2000);
return dtd.promise(); // 返回 promise 對象
}
$.when(wait())
.done(function(){ alert("成功了震放!"); })
.fail(function(){ alert("失敗了!"); });
1.8 普通操作的回調(diào)函數(shù)接口(中)
另一種防止執(zhí)行狀態(tài)被外部改變的方法是驼修,使用 deferred 對象的構(gòu)建函數(shù) $.Deferred()殿遂。
這時,wait 函數(shù)還是保持不變乙各,我們直接將它傳入 $.Deferred:
$.Deferred(wait)
.done(function(){ alert("成功了!"); })
.fail( function(){ alert("失敗了墨礁!"); } );
jQuery 規(guī)定,$.Deferred() 可以接受一個函數(shù)名(注意耳峦,是函數(shù)名)作為參數(shù)恩静,$.Deferred() 所生成的 deferred 對象將作為這個函數(shù)的默認(rèn)參數(shù)。
1.9 普通操作的回調(diào)函數(shù)接口(下)
除了上面兩種方法以外蹲坷,我們還可以直接在 wait 對象上部署 deferred 接口驶乾。
var dtd = $.Deferred(); // 新建一個 deferred 對象
var wait = function(){
var tasks = function(){
alert("執(zhí)行完畢!");
dtd.resolve(); // 改變 deferred 對象的執(zhí)行狀態(tài)
}
setTimeout(tasks,2000);
}
dtd.promise(wait);
wait.done(function(){ alert("成功了循签!"); })
.fail(function(){ alert("失敗了级乐!"); });
wait(dtd);
這里的關(guān)鍵是 dtd.promise(wait); 這一行,它的作用就是在 wait 對象上部署 Deferred 接口县匠。正是因為有了這一行风科,后面才能直接在 wait 上面調(diào)用 done() 和 fail() 方法
1.10 小結(jié):defered 對象的方法
前面已經(jīng)講到了 deferred 對象的多種方法撒轮,下面做一個總結(jié):
(1) $.Deferred() 生成一個 deferred 對象。
(2) deferred.done() 指定操作成功時的回調(diào)函數(shù)贼穆。
(3) deferred.fail() 指定操作失敗時的回調(diào)函數(shù)题山。
(4) deferred.promise() 沒有參數(shù)時,返回一個新的 deferred 對象該對象的運(yùn)行狀態(tài)無法被改變故痊;接受參數(shù)時臀蛛,作用為參數(shù)對象上部署 deferred 接口。
(5) deferred.resolve() 手動改變 deferred 對象的運(yùn)行狀態(tài)為“已完成”崖蜜,從而觸發(fā) done() 方法浊仆。
(6) deferred.reject() 這個方法與 defered.resolve() 正好相反,調(diào)用后將 deferred 對象的運(yùn)行狀態(tài)變?yōu)椤耙咽 痹チ欤瑥亩|發(fā) fail() 方法抡柿。
(7) $.when() 為多個操作指定回調(diào)函數(shù)。
除了這些方法以外等恐,deferred 對象還有兩個重要方法:
(8) deferred.then() :
有時為了省事洲劣,可以把 done() 和 fail() 合在一起寫,這就是 then() 方法:
$.when($.ajax("/main.php"))
.then(successFunc,failFunc)
如果 then() 有兩個參數(shù)课蔬,那么第一個是 done() 方法的回調(diào)函數(shù)囱稽,第二個參數(shù)是 fail() 方法的回調(diào)函數(shù)。如果 then() 只有一個函數(shù)二跋,那么等同于 done()战惊。
(9) deferred.always():
這個方法也是用來指定回調(diào)函數(shù)的,它的作用是扎即,不管調(diào)用的是 deferred.resolve() 還是 deferred.reject()吞获,最后總是執(zhí)行。
$.ajax("test.html")
.always(function(){ alert("已執(zhí)行谚鄙!"); });
<br />
2.Q.js
Q.js 是知名各拷、功能完整的 Promise 函式庫,已經(jīng)實現(xiàn)了 Promise/A 標(biāo)準(zhǔn)闷营。
特點:
- 瀏覽器端和服務(wù)器端共用
- 發(fā)展早烤黍,較其他庫相對成熟
- Github:https://github.com/kriskowal/q
- 官方文檔:http://documentup.com/kriskowal/q/
標(biāo)準(zhǔn)的回調(diào)函數(shù)的方式如下,嵌套比較深:
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// Do something with value4
});
});
});
});
使用Q.js 后的代碼如下傻盟,采用依次排列:
Q.fcall(promisedStep1)
.then(promisedStep2)
.then(promisedStep3)
.then(promisedStep4)
.then(function (value4) {
// Do something with value4
})
.catch(function (error) {
// Handle any error from all above steps
})
.done();
來個簡單示例如下:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Q.js</title>
<script src="../js/q-1.4.1.min.js"></script>
</head>
<body>
<script>
function qtest(num) {
return Q.delay(num, 1000);
}
Q.all([
qtest(10),
qtest(20),
qtest(30)
]).spread(function (x, y, z) {
return x + y + z;
}).done(function (str) {
console.log("The sum is " + str + ".")
});
</script>
</body>
</html>
輸出結(jié)果為:
The sum is 60.
API 文檔地址:https://github.com/kriskowal/q/wiki/API-Reference
<br />
3.Koajs
Koajs 是基于 Node.js 平臺的下一代 Web 開發(fā)框架速蕊。使用 koa 編寫 web 應(yīng)用,通過組合不同的 generator莫杈,可以免除重復(fù)繁瑣的回調(diào)函數(shù)嵌套互例,并極大地提升常用錯誤處理效率。Koa 不在內(nèi)核方法中綁定任何中間件筝闹,它僅僅提供了一個輕量優(yōu)雅的函數(shù)庫媳叨,使得編寫 Web 應(yīng)用變得得心應(yīng)手腥光。
特點:
- 基于 Generator,消滅回調(diào)代碼
- 強(qiáng)大的異常處理
- 實現(xiàn)了基礎(chǔ)的 http 工程糊秆,其他通過 middleware 實現(xiàn)武福、靈活
- 官網(wǎng)地址:http://koajs.com/
- 中文版地址:http://koa.bootcss.com/
示例:
<script>
function * greet(name){
yield "hello " + name + "!";
yield "I hope you are enjoying the generator course";
if(name.startsWith('Q')){
yield "it's cool how your name starts width Q, " + name;
}
yield "see you later!";
}
</script>
在谷歌瀏覽器中執(zhí)行結(jié)果:
中間件(Middle)
實際上,koa有很多第三方開發(fā)的中間件痘番,這些中間件的熟練運(yùn)用才是關(guān)鍵捉片,github 有很多這方面的庫。以下是常用的一些: