大家周末好凡伊,要說最近幾年什么語言大紅大紫唧席,當屬JavaScript了擦盾。話說雖然是10天就創(chuàng)造出的語言,但是人家能文能武淌哟。web前端自然不必多說了迹卢,各種框架你方登罷我上場,前兩年還是Angular一統(tǒng)天下徒仓,這兩年React又是大紅大紫腐碱,還有Vue最近異軍突起,好不紅火掉弛。要是僅僅是前端也就算了症见,但是由于Node.js人家在后臺也能寫,React Native的出現(xiàn)讓人家移動端也能做殃饿。好吧谋作,還有硬件上也出現(xiàn)Ruff方案,好像硬件上也能寫了壁晒。真是讓人感覺挺有意思的事情瓷们。
圖表君上邊叨叨了這么多业栅,難道是為JavaScript唱贊歌的嗎秒咐?呵呵,其實并不是碘裕。只是最近因為在用上篇文章介紹的AWS Lambda携取。Lambda現(xiàn)在只支持Java,Node.js,Python帮孔。最終選擇了Node.js進行開發(fā)雷滋,不可避免的要牽扯到異步操作的問題不撑。那么今天就來聊聊JavaScript中的Promise。
什么是Promise
Promise是異步編程的一種解決方案晤斩,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強大焕檬。它由社區(qū)最早提出和實現(xiàn),ES6將其寫進了語言標準澳泵,統(tǒng)一了用法实愚,原生提供了Promise對象。
所謂Promise兔辅,簡單說就是一個容器腊敲,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果。從語法上說维苔,Promise是一個對象碰辅,從它可以獲取異步操作的消息。Promise提供統(tǒng)一的API介时,各種異步操作都可以用同樣的方法進行處理没宾。
上面是Promise的一個定義,引自阮一峰的ES6標準入門一書潮尝。S6標準入門榕吼。多說一句,目前的JavaScript項目無論是前臺或者是后臺勉失,都應該采用ES6的標準語法來寫羹蚣,ES6讓JavaScript的書寫更加的清晰和規(guī)范。
基本用法
如何來構造一個promise對象呢乱凿?ES6中提供了原生Promise可以使用顽素。
var promise = new Promise(function(resolve, reject) {
// ... here is some code
if (/* 異步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
上面的例子給出了new一個promise對象的方法,Promise的構造函數(shù)接受一個函數(shù)作為參數(shù)傳入徒蟆,這個函數(shù)的兩個參數(shù)胁出,reject和resolve是JavaScript本身提供的兩個函數(shù)。
一個promise對象有三個狀態(tài)分別是段审,pending全蝶,resolved,rejected寺枉。resolve函數(shù)可以將pending狀態(tài)轉變?yōu)閞esolved狀態(tài)抑淫。reject函數(shù)可以講pending狀態(tài)轉變了rejected狀態(tài)。對象的狀態(tài)不受外界的影響姥闪,同樣也是promise名字的由來始苇。外部你拿著我的一個承諾,一會我會告訴你我的狀態(tài)筐喳。
promise對象通過then方法來添加回調(diào)函數(shù)催式。例如這樣
promise.then(data=> console.log(data), err=> console.log(err));
當promise被resolved的時候函喉,就會把data log出來。當promise被rejected的時候荣月,err就會被log出來管呵。
看上去好像是挺簡單的,的確Promise的應用使得異步的操作哺窄,以同步的形式表現(xiàn)出來撇寞。當發(fā)生錯誤的時候可以通過catch方法,來定義回調(diào)函數(shù)堂氯。
怎么用
上邊都是一些干巴巴的定義蔑担,那么到底該怎么用呢?Promise又怎么樣的解決了問題呢咽白,下邊我們看一個例子啤握。假設下邊一個場景,我們一個服務晶框,從一個外邊service獲取數(shù)據(jù)排抬,然后寫到一個db里,或者一個存儲里授段,最后在把存儲的狀態(tài)龍出來蹲蒲,那么如果沒有promise是怎么寫的呢?可能會是這樣侵贵。
getData(function (value1) {
storeToDb(value1, function(value2) {
logStore(value2, function(value3) {
//...
});
});
});
傳統(tǒng)的回調(diào)的寫法届搁,這樣使得代碼邏輯混亂在一起。再想想如果再加上錯誤處理的情況窍育,更是酸爽卡睦。那么用promise來寫會怎么樣呢?看下邊這樣的代碼
function getData(){
return new Promise((resolve,reject) =>{
// ... send request to get data
if(/* get successfully*/){
resolve(data)
}else{
reject(err)
}
})
}
function storeData(data){
return new Promise((resolve,reject)=>{
// ... store the data
if(/*store successfully*/){
resolve(data)
}else{
reject(err)
}
})
}
getData()
.then(data => storeData(data))
.then(data => console.log('the process is done',data));
.catch(err => console.error('there is the err',err));
這樣寫是不是就是很清楚了漱抓,先getData表锻,然后再storeData,最后將這次運行的情況log了出來乞娄,其中有任何的問題瞬逊,在catch中都可以Catch出來。代碼的邏輯以同步的方式得到了體現(xiàn)仪或。我們來看看如果是其他語言會怎么寫确镊,下邊是個ruby的語言的例子
def get_data
// ...send request
if /*get successfully */
return data
else
raise GetDataError
end
end
def store_data
// ...save to db
if /*save successfully */
return data
else
raise StoreDataError
end
end
/*Main Logic*/
begin
request_data = get_data
db_data = store_data request_data
p "here is the store data #{db_data}"
rescue e
p "here is some errors #{e}"
end
我們對比兩個例子,可以看到在使用的Promise后讓JavaScript的異步方式的編程模式更將清楚溶其,也更加讓人容易理解骚腥。
由于JavaScript的執(zhí)行環(huán)境是單線程的敦间,所以大量采用了異步的方式來進行編程瓶逃,這使得我們寫起代碼并不十分符合我們一般的習慣束铭。但是Promise的出現(xiàn)讓這種問題能得到一定程度的緩解。
但是異步操作異步操作的好處厢绝,比如上邊的那個例子契沫,如果我們想要做的同時并發(fā)10個操作,那個在ruby或者其他語言中中就要啟多個線程來進行昔汉。但是JavaScript就完全沒有這個問題懈万。只要簡單的loop下就行了。
但是如果我們想要在這10個操作完成后根據(jù)返回的狀態(tài)做點其他操作該怎么做呢靶病?這時候用Promise.all就是最好的了会通。
let p = Promise.all([p1, p2, p3]);
Promise.all接受數(shù)組作為參數(shù)傳入,每個元素都是一個promise對象娄周。只要所有子promise都resolved以后涕侈,p才會被resolved。只要有一個被rejected煤辨,這個p就會被rejected裳涛。但是有一點是這些子promise之間并不會有順序的關系。再來看一個例子:
var guid = 0;
function run() {
guid++;
var id = guid;
return new Promise(resolve => {
setTimeout(function () {
console.log(id);
resolve(id);
}, (Math.random() * 1.5 | 0) * 1000);
});
}
var promises = Array.from({ length: 10 }, run);
Promise.all(promises)
OUTPUT:
2
3
5
6
7
8
10
1
4
9
從這次的output可以看到众辨,promise之間并沒有順序執(zhí)行端三,實際上是并發(fā)的。那么如何讓這些promise是順序執(zhí)行呢鹃彻?留個大家自己思考下郊闯,下篇文章,我們揭曉蛛株⌒樾觯或者可以聯(lián)系圖表君,私下告訴你答案哦泳挥。
ps然痊,當然也可以用一些第三方的庫和方案,例如(async)來實現(xiàn)順序操作屉符,但是代碼的樂趣不就是做些思維挑戰(zhàn)嗎:)