相信之前用過(guò)JavaScript的朋友都碰到過(guò)異步回調(diào)地獄(callback hell),N多個(gè)回調(diào)的嵌套不僅讓代碼讀起來(lái)十分困難,維護(hù)起來(lái)也很不方便。
其實(shí)C#在Task
出現(xiàn)之前也是有類似場(chǎng)景的,Async Programming Mode時(shí)代,用Action
和Func
做回調(diào)也很流行,不過(guò)也是意識(shí)到太多的回調(diào)嵌套代碼可讀性差且維護(hù)不易,微軟引入了Task
和Task-based Async Pattern。
雖然不知道是哪個(gè)語(yǔ)言最早有這個(gè)概念,但相信是C#把async await
帶到流行語(yǔ)言的舞臺(tái),接著其他語(yǔ)言也以不同的形式支持async await
,如Python, Dart, Swift等。
JavaScript同樣在ES6開(kāi)始支持Promise
和Generator
,并在ES7中提出支持async await
的議案。
這篇先來(lái)看看Promise:
Promise
之于TypeScript,相當(dāng)于Task
之于C#,只有返回Promise
的函數(shù)才能使用async await
。Promise
其實(shí)就是一個(gè)可以獲取異步結(jié)果,并封裝了一些異步操作的對(duì)象。
有三個(gè)狀態(tài):pending
: 進(jìn)行中resolved
: 成功rejected
: 失敗
并且這三個(gè)狀態(tài)只有兩種轉(zhuǎn)換:pending
->resolved
、pending
->rejected
,不是成功就是失敗,并沒(méi)有多余的狀態(tài)轉(zhuǎn)換。
這兩種轉(zhuǎn)換都是由異步返回的結(jié)果給定的,成功取回?cái)?shù)據(jù)就是resolved
,取數(shù)據(jù)出異常就是rejected
。
也因此,這轉(zhuǎn)換過(guò)后的結(jié)果就是固定的了,不可能在轉(zhuǎn)換過(guò)后還會(huì)變回pending
或其他狀態(tài)。Promise
不能在任務(wù)進(jìn)行中取消,只能等結(jié)果返回,這點(diǎn)上不如C#的Task
,Task
可以通過(guò)CancelTaskToken
來(lái)取消任務(wù)。
可以直接new一個(gè)Promise
對(duì)象,構(gòu)造函數(shù)的參數(shù)是一個(gè)有兩個(gè)參數(shù)的函數(shù)。
這兩個(gè)參數(shù)一個(gè)是resove
,用來(lái)在異步操作成功后調(diào)用,并把異步結(jié)果傳出去,調(diào)用resove
后狀態(tài)就由pending
->resolved
。
另一個(gè)是reject
,用來(lái)在失敗或異常時(shí)調(diào)用,并把錯(cuò)誤消息傳出去,調(diào)用reject
后狀態(tài)由pending
->rejected
。
var promise = new Promise(function(resolve, reject) { });
通常需要在成功或失敗后做一些操作,這時(shí)需要then
來(lái)做這個(gè)事,then
可以有兩個(gè)函數(shù)參數(shù),第一個(gè)是成功后調(diào)用的,第二個(gè)是失敗調(diào)用的,第二個(gè)是可選的。
另外,then
返回的也是一個(gè)Promise,不過(guò)不是原來(lái)的那個(gè),而是新new出來(lái)的,這樣可以鏈?zhǔn)秸{(diào)用,then
后面再接then
。
// 函數(shù)參數(shù)用lambda表達(dá)式寫(xiě)更簡(jiǎn)潔promise.then(success => { console.info(success); }, error => { console.info(error); }).then(()=>console.info('finish'));
在實(shí)際場(chǎng)景中,我們可能需要在一個(gè)異步操作后再接個(gè)異步操作,這樣就會(huì)有Promise
的嵌套操作。
下面的代碼顯示的是Promise
的嵌套操作:p1
先打印"start",延時(shí)兩秒打印"p1"。p2
在p1
完成后延時(shí)兩秒打印"p2"。
function delay(): Promise{ return new Promise ((resolve, reject)=>{setTimeout(()=>resolve(), 2000)}); }let p1 = new Promise((resolve, reject) => { console.info('start'); delay().then(()=>{ console.info('p1'); resolve() }); });let p2 = new Promise((resolve, reject) => { p1.then(()=>delay().then(()=>resolve())); }); p2.then(()=>console.info('p2'));
上面提到Promise
出錯(cuò)時(shí)把狀態(tài)變?yōu)?code>rejected并把錯(cuò)誤消息傳給reject
函數(shù),在then
里面調(diào)用reject
函數(shù)就可以顯示異常。
不過(guò)這樣寫(xiě)顯得不是很友好,Promise
還有個(gè)catch
函數(shù)專門用來(lái)處理錯(cuò)誤異常。
而且Promise
的異常是冒泡傳遞的,最后面寫(xiě)一個(gè)catch
就可以捕獲到前面所有promise可能發(fā)生的異常,如果用reject
就需要每個(gè)都寫(xiě)。
所以reject
函數(shù)一般就不需要在then
里面寫(xiě),在后面跟個(gè)catch
就可以了。
new Promise(function(resolve, reject) { throw new Error('error'); }).catch(function(error) { console.info(error); // Error: error});
也如上面所說(shuō)狀態(tài)只有兩種變化且一旦變化就固定下來(lái),所以如果已經(jīng)在Promise
里執(zhí)行了resolve
,再throw異常是沒(méi)用的,catch不到,因?yàn)闋顟B(tài)已經(jīng)變成resolved
。
new Promise(function(resolve, reject) { resolve('success'); throw new Error('error'); }).catch(function(error) { console.info(error); // 不會(huì)執(zhí)行到這里});
另外,catch
里的代碼也可能出異常,所以catch
后面也還可以跟catch
的議案。
new Promise(function(resolve, reject) { throw new Error('error'); }).catch(function(error) { console.info(error); // Error: error throw new Error('catch error'); }).catch(function(error){ console.info(error); // Error: catch error };
異常的try...catch
后面可以跟finally
來(lái)執(zhí)行必須要執(zhí)行的代碼,Promise
原生并不支持,可以引入BlueBird的擴(kuò)展庫(kù)來(lái)支持。
另外還有done
在最后面來(lái)表示執(zhí)行結(jié)束并拋出可能出現(xiàn)的異常,比如最后一個(gè)catch
代碼塊里的異常。
let p = new Promise(function(resolve, reject) { x = 2; // error, 沒(méi)有聲明x變量 resolve('success'); }).catch(function(error) { console.info(error); }).finally(()=>{ // 總會(huì)執(zhí)行這里 console.info('finish'); y = 2; // error, 沒(méi)有聲明y變量}).done(); try{ p.then(()=>console.info('done')); } catch (e){ console.info(e); // 由于最后面的done,所以會(huì)把finally里的異常拋出來(lái),如果沒(méi)有done則不會(huì)執(zhí)行到這里}
雖然JavaScript是單線程語(yǔ)言,但并不妨礙它執(zhí)行一些IO并行操作,如不阻塞發(fā)出http request,然后異步等待。Promise
除了用then
來(lái)順序執(zhí)行外,也同樣可以不阻塞同時(shí)執(zhí)行多個(gè)Promise
然后等所有結(jié)果返回再進(jìn)行后續(xù)操作。
C#的Task
有個(gè)WhenAll
的靜態(tài)方法來(lái)做這個(gè)事,Promise
則是用all
方法達(dá)到同樣目的。all
方法接受實(shí)現(xiàn)Iterator接口的對(duì)象,比如數(shù)組。
let p = Promise.all([p1, p2, p3]);
all
返回的是一個(gè)新的Promise
- p,p的狀態(tài)是由p1, p2, p3同時(shí)決定的:
p.resolved = p1.resolve && p2.resolve && p3.resolve p.rejected = p1.rejected || p2.rejected || p3.rejected
也就是說(shuō)p的成功需要p1,p2,p3都成功,而只要p1, p2, p3里有任何一個(gè)失敗則p失敗并退出。
Promise
還有一個(gè)方法race
同樣是并行執(zhí)行多個(gè)Promise
,不同于all
的是它的成功狀態(tài)和錯(cuò)誤狀態(tài)一樣,只要有一個(gè)成功就成功,如同C# Task的Any
方法。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無(wú)理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。