這篇文章主要介紹了NodeJS中利用Promise來(lái)封裝異步函數(shù),使用統(tǒng)一的鏈?zhǔn)紸PI來(lái)擺脫多重回調(diào)的噩夢(mèng),非常的實(shí)用的小技能,希望小伙伴們能夠喜歡
在寫(xiě)Node.js的過(guò)程中,連續(xù)的IO操作可能會(huì)導(dǎo)致“金字塔噩夢(mèng)”,回調(diào)函數(shù)的多重嵌套讓代碼變的難以維護(hù),利用CommonJs的Promise來(lái)封裝異步函數(shù),使用統(tǒng)一的鏈?zhǔn)紸PI來(lái)擺脫多重回調(diào)的噩夢(mèng)。
Node.js提供的非阻塞IO模型允許我們利用回調(diào)函數(shù)的方式處理IO操作,但是當(dāng)需要連續(xù)的IO操作時(shí),你的回調(diào)函數(shù)會(huì)多重嵌套,代碼很不美觀,而且不易維護(hù),而且可能會(huì)有許多錯(cuò)誤處理的重復(fù)代碼,也就是所謂的“Pyramid of Doom”。
代碼如下:
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// Do something with value4
});
});
});
});
這其實(shí)就是Node.js的Control flow的問(wèn)題,對(duì)于這個(gè)問(wèn)題,解決方案都許多,比如利用async,或者eventProxy等,不過(guò)本文的主題是利用CommonJs規(guī)范中對(duì)Promise來(lái)解決這個(gè)問(wèn)題。
什么是Promise?
CommonJs的Promise規(guī)范有許多種,我們一般討論的是Promise/A+規(guī)范,它定義了Promise的基本行為。
Promise是一個(gè)對(duì)象,它通常代表一個(gè)在未來(lái)可能完成的異步操作。這個(gè)操作可能成功也可能失敗,所以一個(gè)Promise對(duì)象一般有3個(gè)狀態(tài):Pending,F(xiàn)ulfilled,Rejected。分別代表未完成、成功完成和操作失敗。一旦Promise對(duì)象的狀態(tài)從Pending變成Fulfilled或者Rejected任意一個(gè),它的狀態(tài)都沒(méi)有辦法再被改變。
一個(gè)Promise對(duì)象通常會(huì)有一個(gè)then方法,這個(gè)方法讓我們可以去操作未來(lái)可能成功后返回的值或者是失敗的原因。這個(gè)then方法是這樣子的:
promise.then(onFulfilled, onRejected)
顯而易見(jiàn)的是,then方法接受兩個(gè)參數(shù),它們通常是兩個(gè)函數(shù),一個(gè)是用來(lái)處理操作成功后的結(jié)果的,另一個(gè)是用來(lái)處理操作失敗后的原因的,這兩個(gè)函數(shù)的第一個(gè)參數(shù)分別是成功后的結(jié)果和失敗的原因。如果傳給then方法的不是一個(gè)函數(shù),那么這個(gè)參數(shù)會(huì)被忽略。
then方法的返回值是一個(gè)Promise對(duì)象,這一個(gè)特點(diǎn)允許我們鏈?zhǔn)秸{(diào)用then來(lái)達(dá)到控制流程的效果。這里有許多細(xì)節(jié)上的問(wèn)題,比如值的傳遞或者錯(cuò)誤處理等。Promise的規(guī)范是這樣定義的:
onFulfilled或者onRejected函數(shù)的返回值不是Promise對(duì)象,則該值將會(huì)作為下一個(gè)then方法中onFulfilled的第一個(gè)參數(shù),如果返回值是一個(gè)Promise對(duì)象,怎么then方法的返回值就是該P(yáng)romise對(duì)象
onFulfilled或者onRejected函數(shù)中如果有異常拋出,則該then方法的返回的Promise對(duì)象狀態(tài)轉(zhuǎn)為Rejected,如果該P(yáng)romise對(duì)象調(diào)用then,則Error對(duì)象會(huì)作為onRejected函數(shù)的第一個(gè)參數(shù)
如果Promise狀態(tài)變?yōu)镕ulfilled而在then方法中沒(méi)有提供onFulfilled函數(shù),則then方法返回的Promise對(duì)象狀態(tài)變?yōu)镕ulfilled且成功的結(jié)果為上一個(gè)Promise的結(jié)果,Rejected同理。
補(bǔ)充一句,onFulfilled和onRejected都是異步執(zhí)行的。
規(guī)范的實(shí)現(xiàn):q
上面講的是Promise的規(guī)范,而我們需要的是它的實(shí)現(xiàn),q是一個(gè)對(duì)Promise/A+有著較好實(shí)現(xiàn)規(guī)范的庫(kù)。
首先我們需要?jiǎng)?chuàng)建一個(gè)Promise對(duì)象,關(guān)于Promise對(duì)象創(chuàng)建的規(guī)范在Promise/B中,這里不做詳細(xì)的解釋?zhuān)苯由洗a。
代碼如下:
function(flag){
var defer = q.defer();
fs.readFile("a.txt", function(err, data){
if(err) defer.reject(err);
else defer.resolve(data);
});
return defer.promise;
}
多數(shù)Promise的實(shí)現(xiàn)在Promise的創(chuàng)建上大同小異,通過(guò)創(chuàng)建一個(gè)具有promise屬性的defer對(duì)象,如果成功獲取到值則調(diào)用defer.resolve(value),如果失敗,則調(diào)用defer.reject(reason),最后返回defer的promise屬性即可。這個(gè)過(guò)程可以理解為調(diào)用defer.resolve將Promise的狀態(tài)變成Fulfilled,調(diào)用defer.reject將Promise的狀態(tài)變成Rejected。
在面對(duì)一系列連續(xù)的異步方法時(shí),怎么利用Promise寫(xiě)出漂亮的代碼呢?看下下面的例子。
代碼如下:
promise0.then(function(result){
// dosomething
return result;
}).then(function(result) {
// dosomething
return promise1;
}).then(function(result) {
// dosomething
}).catch(function(ex) {
console.log(ex);
}).finally(function(){
console.log("final");
});
在上面的代碼中,then方法只接受OnFulfilled,而catch方法實(shí)際上就是then(null, OnRejected),這樣的話只要一系列異步方法只要始終是成功返回值的,那么代碼就會(huì)瀑布式的向下運(yùn)行,如果其中任意一個(gè)異步方法失敗或者發(fā)生異常,那么根據(jù)CommonJs的Promise規(guī)范,將執(zhí)行catch中的function。q還提供了finally方法,從字面上也很好理解,就是不論resolve還是reject,最終都會(huì)執(zhí)行finally中的function。
看上去似乎不錯(cuò),代碼更以維護(hù)且美觀了,那么如果希望并發(fā)呢?
代碼如下:
q.all([promise0, promise1, promise2]).spread(function(val0, val1, val2){
console.log(arguments);
}).then(function(){
console.log("done");
}).catch(function(err){
console.log(err);
});
q也為并發(fā)提供了api,調(diào)用all方法并傳遞一個(gè)Promise數(shù)組即可繼續(xù)使用then的鏈?zhǔn)斤L(fēng)格。還有像q.nfbind等可以將Node.js的原生API轉(zhuǎn)化成Promise來(lái)統(tǒng)一代碼格式也是挺好的。更多api在這里就不一一詳述了。
結(jié)論
本文主要介紹通過(guò)使用Promise來(lái)解決Node.js控制流問(wèn)題,但Promise也可同樣應(yīng)用于前端,EMCAScript6已經(jīng)提供了原生的API支持。需要指出的是Promise并不是唯一的解決方案,async也是一個(gè)很好的選擇,并且提供更友好的并發(fā)控制API,不過(guò)我覺(jué)得Promise在封裝具有異步方法的函數(shù)時(shí)更具優(yōu)勢(shì)。
好了,本文就先到這里了,希望對(duì)大家能夠有所幫助。
更多信息請(qǐng)查看IT技術(shù)專(zhuān)欄