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