從C#到TypeScript - Promise

總目錄

從C#到TypeScript - Promise

背景

相信以前用過JavaScript的朋友都碰到過異步回調地獄(callback hell),N多個回調的嵌套不只讓代碼讀起來十分困難,維護起來也很不方便。
其實C#在Task出現以前也是有相似場景的,Async Programming Mode時代,用ActionFunc作回調也很流行,不過也是意識到太多的回調嵌套代碼可讀性差且維護不易,微軟引入了Task和Task-based Async Pattern。
雖然不知道是哪一個語言最先有這個概念,但相信是C#把async await帶到流行語言的舞臺,接着其餘語言也以不一樣的形式支持async await,如Python, Dart, Swift等。
JavaScript一樣在ES6開始支持PromiseGenerator,並在ES7中提出支持async await的議案。

這篇先來看看Promise:

Promise的特色

Promise之於TypeScript,至關於Task之於C#,只有返回Promise的函數才能使用async await
Promise其實就是一個能夠獲取異步結果,並封裝了一些異步操做的對象。
有三個狀態:
pending: 進行中
resolved: 成功
rejected: 失敗
而且這三個狀態只有兩種轉換:pending->resolvedpending->rejected,不是成功就是失敗,並無多餘的狀態轉換。
這兩種轉換都是由異步返回的結果給定的,成功取回數據就是resolved,取數據出異常就是rejected
也所以,這轉換事後的結果就是固定的了,不可能在轉換事後還會變回pending或其餘狀態。
Promise不能在任務進行中取消,只能等結果返回,這點上不如C#的TaskTask能夠經過CancelTaskToken來取消任務。

Promise的使用

能夠直接new一個Promise對象,構造函數的參數是一個有兩個參數的函數。
這兩個參數一個是resove,用來在異步操做成功後調用,並把異步結果傳出去,調用resove後狀態就由pending->resolved
另外一個是reject,用來在失敗或異常時調用,並把錯誤消息傳出去,調用reject後狀態由pending->rejected

var promise = new Promise(function(resolve, reject) {
    
});

一般須要在成功或失敗後作一些操做,這時須要then來作這個事,then能夠有兩個函數參數,第一個是成功後調用的,第二個是失敗調用的,第二個是可選的。
另外,then返回的也是一個Promise,不過不是原來的那個,而是新new出來的,這樣能夠鏈式調用,then後面再接then

// 函數參數用lambda表達式寫更簡潔
promise.then(success => {
    console.info(success);
}, error => {
    console.info(error);
}).then(()=>console.info('finish'));

嵌套的Promise

在實際場景中,咱們可能須要在一個異步操做後再接個異步操做,這樣就會有Promise的嵌套操做。
下面的代碼顯示的是Promise的嵌套操做:
p1先打印"start",延時兩秒打印"p1"。
p2p1完成後延時兩秒打印"p2"。

function delay(): Promise<void>{
    return new Promise<void>((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出錯時把狀態變爲rejected並把錯誤消息傳給reject函數,在then裏面調用reject函數就能夠顯示異常。
不過這樣寫顯得不是很友好,Promise還有個catch函數專門用來處理錯誤異常。
並且Promise的異常是冒泡傳遞的,最後面寫一個catch就能夠捕獲到前面全部promise可能發生的異常,若是用reject就須要每一個都寫。
因此reject函數通常就不須要在then裏面寫,在後面跟個catch就能夠了。

new Promise(function(resolve, reject) {
  throw new Error('error');
}).catch(function(error) {
  console.info(error); // Error: error
});

也如上面所說狀態只有兩種變化且一旦變化就固定下來,因此若是已經在Promise裏執行了resolve,再throw異常是沒用的,catch不到,由於狀態已經變成resolved

new Promise(function(resolve, reject) {
    resolve('success');
    throw new Error('error');
}).catch(function(error) {
    console.info(error); // 不會執行到這裏
});

另外,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   
};

BlueBird的 finally 和 done

異常的try...catch後面能夠跟finally來執行必需要執行的代碼,Promise原生並不支持,能夠引入BlueBird的擴展庫來支持。
另外還有done在最後面來表示執行結束並拋出可能出現的異常,好比最後一個catch代碼塊裏的異常。

let p = new Promise(function(resolve, reject) {
    x = 2;  // error, 沒有聲明x變量
    resolve('success');
}).catch(function(error) {
    console.info(error); 
}).finally(()=>{ // 總會執行這裏
    console.info('finish');
    y = 2;  // error, 沒有聲明y變量
}).done(); 

try{
    p.then(()=>console.info('done'));
} catch (e){
    console.info(e); // 因爲最後面的done,因此會把finally裏的異常拋出來,若是沒有done則不會執行到這裏
}

並行執行Promise

雖然JavaScript是單線程語言,但並不妨礙它執行一些IO並行操做,如不阻塞發出http request,而後異步等待。
Promise除了用then來順序執行外,也一樣能夠不阻塞同時執行多個Promise而後等全部結果返回再進行後續操做。
C#的Task有個WhenAll的靜態方法來作這個事,Promise則是用all方法達到一樣目的。
all方法接受實現Iterator接口的對象,好比數組。

let p = Promise.all([p1, p2, p3]);

all返回的是一個新的Promise- p,p的狀態是由p1, p2, p3同時決定的:

p.resolved = p1.resolve && p2.resolve && p3.resolve
p.rejected = p1.rejected || p2.rejected || p3.rejected

也就是說p的成功須要p1,p2,p3都成功,而只要p1, p2, p3裏有任何一個失敗則p失敗並退出。

Promise還有一個方法race一樣是並行執行多個Promise,不一樣於all的是它的成功狀態和錯誤狀態同樣,只要有一個成功就成功,如同C# Task的Any方法。

let p = Promise.race([p1, p2, p3]);
相關文章
相關標籤/搜索