你可能知道介杆,javascript在瀏覽器端是一門單線程的語言(single thread)鹃操。道理很簡(jiǎn)單椰拒,就是設(shè)計(jì)者當(dāng)時(shí)設(shè)計(jì)這門語言的時(shí)候只是考慮到這門語言就是一個(gè)腳本語言,應(yīng)該足夠簡(jiǎn)單缆毁,要易于上手。
但是慢慢的發(fā)展就會(huì)產(chǎn)生一個(gè)問題,單線程一次只能執(zhí)行一個(gè)任務(wù),如果有多個(gè)任務(wù)同時(shí)執(zhí)行,就必須排隊(duì),前面一個(gè)任務(wù)完成才能執(zhí)行后面一個(gè)任務(wù),以此類推。
這種模式下你不用考慮復(fù)雜的線程安全滨溉,也不用考慮線程通信得哆,故出發(fā)點(diǎn)是好的。但是有一個(gè)壞處,會(huì)發(fā)生線程阻塞,就是一個(gè)耗時(shí)很長(zhǎng)的任務(wù)如果沒有執(zhí)行完,就會(huì)阻塞之后任務(wù)的執(zhí)行。通俗的講就是會(huì)造成瀏覽器的假死
為了解決這個(gè)問題,Javascript語言將任務(wù)的執(zhí)行模式分成兩種:同步(Synchronous)和異步(Asynchronous)作箍。
- "同步模式"就是上一段的模式屹电,后一個(gè)任務(wù)等待前一個(gè)任務(wù)結(jié)束外莲,然后再執(zhí)行,程序的執(zhí)行順序與任務(wù)的排列順序是一致的、同步的;"異步模式"則完全不同,每一個(gè)任務(wù)有一個(gè)或多個(gè)回調(diào)函數(shù)(callback),前一個(gè)任務(wù)結(jié)束后,不是執(zhí)行后一個(gè)任務(wù),而是執(zhí)行回調(diào)函數(shù),后一個(gè)任務(wù)則是不等前一個(gè)任務(wù)結(jié)束就執(zhí)行,所以程序的執(zhí)行順序與任務(wù)的排列順序是不一致的、異步的吵冒。
-
"異步模式"非常重要。在瀏覽器端南捂,耗時(shí)很長(zhǎng)的操作都應(yīng)該異步執(zhí)行,避免瀏覽器失去響應(yīng),最好的例子就是Ajax操作。在服務(wù)器端,"異步模式"甚至是唯一的模式矗烛,因?yàn)閳?zhí)行環(huán)境是單線程的股冗,如果允許同步執(zhí)行所有http請(qǐng)求怯疤,服務(wù)器性能會(huì)急劇下降,很快就會(huì)失去響應(yīng)。
本文總結(jié)了"異步模式"編程的4種方法湘纵,理解它們可以讓你寫出結(jié)構(gòu)更合理、性能更出色痰哨、維護(hù)更方便的Javascript程序撬讽。
提示:異步不代表加快執(zhí)行,通常我們說的異步是異步I/O鸯绿,我們知道I/O操作遠(yuǎn)沒有CPU的處理速度快瓶蝴。當(dāng)我們?nèi)フ?qǐng)求一個(gè)HTTP服務(wù)男窟、查詢一段SQL歉眷,讀取一個(gè)文件的時(shí)候春缕,會(huì)出現(xiàn)CPU占用不完全的情況米间,這個(gè)時(shí)候CPU必須要等待I/O操作完成,這個(gè)等待時(shí)間是不定的稚配,就會(huì)導(dǎo)致CPU資源浪費(fèi)畅涂。
合理的解決辦法就是采用異步操作,當(dāng)處于I/O等待狀態(tài)就將CPU轉(zhuǎn)換到其它任務(wù)當(dāng)中道川,I/O結(jié)束等待后就重新獲得該方法的控制權(quán)繼續(xù)執(zhí)行午衰。
回調(diào)函數(shù)
JavaScript語言對(duì)異步編程的實(shí)現(xiàn)立宜,就是回調(diào)函數(shù)。所謂回調(diào)函數(shù)苇经,就是把任務(wù)的第二段單獨(dú)寫在一個(gè)函數(shù)里面赘理,等到重新執(zhí)行這個(gè)任務(wù)的時(shí)候,就直接調(diào)用這個(gè)函數(shù)扇单。它的英語名字callback商模,直譯過來就是"重新調(diào)用"。
先看一個(gè)最簡(jiǎn)單的回調(diào)
function f1(callback){
//setTimeout是最簡(jiǎn)單的回調(diào)函數(shù)蜘澜,等待一段時(shí)間后執(zhí)行
setTimeout(function () {
// f1的任務(wù)代碼
callback();
}, 1000);
}
當(dāng)我們執(zhí)行f1這個(gè)函數(shù)的時(shí)候施流,我們會(huì)加載一個(gè)定時(shí)器方法,1秒過后鄙信,會(huì)執(zhí)行它的回調(diào)函數(shù)
讀取文件進(jìn)行處理瞪醋,是這樣寫的。
fs.readFile('/etc/passwd', function (err, data) {
if (err) throw err;
console.log(data);
});console.log(data);});
上面代碼讀取了一個(gè)文件装诡,回調(diào)函數(shù)有兩個(gè)參數(shù)银受,第一個(gè)為錯(cuò)誤對(duì)象,第二個(gè)為讀取到的數(shù)據(jù)
讓情況稍微復(fù)雜一點(diǎn)
看下面這個(gè)代碼
$.post("/ajax/post1",function(data){
if(data.type=="book"){
$.post("/ajax/book",function(data1){
console.log(data1);
});
}else if(data.type=="article"){
$.post("/ajax/article",function(data2){
console.log(data2);
});
}
});
這個(gè)例子先請(qǐng)求了一個(gè)服務(wù)鸦采,根據(jù)返回的結(jié)果宾巍,判斷它是哪一種狀態(tài),在根據(jù)它的狀態(tài)又請(qǐng)求了一次服務(wù)渔伯。這個(gè)例子看起來還比較好受顶霞,我們?cè)诳聪旅孢@種狀態(tài),是不是大家都很熟悉锣吼。
});
});
});
});
});
});
});
});
});
});
});
上面這段代碼就是javascript著名的回調(diào)陷阱选浑,多次的嵌套回調(diào)會(huì)讓你的代碼亂成一團(tuán),并且難以維護(hù)
所以機(jī)智的開發(fā)人員為了解決這個(gè)問題玄叠,他們采取了很多種辦法古徒,咱們從簡(jiǎn)單的開始分析
異步函數(shù)式類庫
- Async.js
- When.js
- parallel.js
- ...
上面這些庫有一個(gè)共同點(diǎn),就是通過函數(shù)的方式把異步方法嵌套在了一起诸典,其實(shí)異步的本質(zhì)還是沒有改變描函,他們都基于Promises/A規(guī)范。
而且這些庫還有一個(gè)共通點(diǎn)狐粱,支持同步方法的隊(duì)列執(zhí)行舀寓,這個(gè)在js動(dòng)畫、集合處理等用的比較多肌蜻,這里不多闡述互墓。
我們這里拿When.js來講解,首先蒋搜,我們看一段代碼:
var getData = function(callback) {
$.getJSON(api, function(data){
callback(data[0]);
});
}
var getImg = function(src, callback) {
var img = new Image();
img.onload = function() {
callback(img);
};
img.src = src;
}
var showImg = function(img) {
$(img).appendTo($('#container'));
}
getData(function(data) {
getImg(data, function(img) {
showImg(img);
});
});
這段代碼完成了三個(gè)任務(wù):1)獲取數(shù)據(jù)篡撵;2)加載圖片判莉;3)顯示圖片,其中育谬,任務(wù)1和2是異步券盅,3是同步,使用的是最常見的callback機(jī)制來處理異步邏輯膛檀,好處是淺顯易懂锰镀,缺點(diǎn)是強(qiáng)耦合、不直觀咖刃、處理異常麻煩等等泳炉。
另外一種常見的實(shí)現(xiàn)異步編程的方案是事件監(jiān)聽器,例如使用QWrap的CustEvent嚎杨,讓任務(wù)成功時(shí)fireEvent花鹅,那么注冊(cè)了這個(gè)Event的監(jiān)聽器就可以收到這個(gè)事件,并收到事件傳遞過來的數(shù)據(jù)枫浙,Dom標(biāo)準(zhǔn)事件也是采用的這種形式刨肃。這種方案也很好理解,代碼從略箩帚。事件監(jiān)聽可以解耦之景,可以綁定任意多個(gè)監(jiān)聽器,但是依然不直觀膏潮,而且事件發(fā)生之后再綁定的監(jiān)聽器也得不到觸發(fā)。
我們嘗試用when.js改寫下這段代碼
var getData = function () {
var deferred = when.defer();
$.getJSON(api, function (data) {
deferred.resolve(data[0]);
});
return deferred.promise;
}
var getImg = function (src) {
var deferred = when.defer();
var img = new Image();
img.onload = function () {
deferred.resolve(img);
};
img.src = src;
return deferred.promise;
}
var showImg = function (img) {
$(img).appendTo($('#container'));
}
getData()
.then(getImg)
.then(showImg);
看最后三行代碼满力,是不是一目了然焕参,非常的語義化?
他解決了一個(gè)問題油额,就是我們前面提到的嵌套陷阱叠纷!
OK!下一章讓我們來看一下Whenjs還有什么功能潦嘶,當(dāng)然這里的重點(diǎn)不是這個(gè)庫涩嚣,大家感興趣的話可以自己去看看when.js,我們的重點(diǎn)是Promises/A規(guī)范
下一章我們繼續(xù)
Promises/A規(guī)范詳細(xì)闡述說明