JS異步編程之Promise詳解,這才是你所需要的前端知識!

Promise是異步編程的解決方案之一,相比傳統的回調和事件機制更為合理和強大。

場景舉例

某天,突發奇想,發了封郵件給木匠師傅,定製一個如此這般的傢俱。

木匠有求必應,即是說,郵件一旦發出就得到了他的承諾(Promise):在下一定盡力。

郵件中規定好了結果的通知方式:

成功了,直接將傢俱(res)郵遞(resolve)過來。

失敗了,直接將失敗的信息(err)發郵件(reject)過來。

JS異步編程之Promise詳解,這才是你所需要的前端知識!

郵件發出等價於得到木匠的承諾P,之後,能做的只有等待(then)。

JS異步編程之Promise詳解,這才是你所需要的前端知識!

行為特徵

狀態

每個Promise有三種狀態:進行中(pending)、已成功(resolved)和已失敗(rejected)。

創建即進入pending狀態,在傳入方法中一旦調用了resolve/reject方法,最終狀態便變成resolved/rejected。

一旦變成結果狀態,即更改成resolved/rejected,狀態便被冷凍,不能再被更改。

1.狀態容器

Promise實質是個狀態容器。

得到結果狀態後,任何時候都可以訪問到此狀態。

這與事件訂閱通知不同,如果訂閱發生在通知之後,訂閱是不起作用的。

2.狀態不可控

一旦創建Promise,便會立刻執行,無法取消。

處於pending狀態時,無法得知進程具體的信息,比如完成百分比(雖然可以自行設置回調進行通知)。

3.失敗的狀態

成功的狀態只能由resolve方法轉成。

失敗的狀態可以由reject方法轉成,也可以由拋出錯誤間接轉成。

三者都會正常的打印出失敗的信息。

JS異步編程之Promise詳解,這才是你所需要的前端知識!

4.錯誤的報告機制

如果失敗狀態沒有接收失敗的回調函數接收,Promise會拋出錯誤。

這裡的拋出錯誤,僅僅是在控制檯顯示之類的提示,不會終止程序的進程。

先打印出 'err' ,再報錯。

JS異步編程之Promise詳解,這才是你所需要的前端知識!

一旦Promise設置了失敗回調函數,即便是代碼執行錯誤,也會自行消化,不外報。

雖然 a 未被定義,但全程安靜,無槽點。

JS異步編程之Promise詳解,這才是你所需要的前端知識!

執行順序

1.傳入方法

創建Promise的同時也會執行傳入方法。

傳入方法不會因為調用了resolve/reject便終止執行,所以更優的方式是retrun resolve/reject。

JS異步編程之Promise詳解,這才是你所需要的前端知識!

2.回調方法

立即得到結果的Promise,其回調函數依然會晚於本輪事件執行。

這種後執行不同於setTimeout的將執行函數push到執行棧,而是將執行函數放到本輪的末尾。

JS異步編程之Promise詳解,這才是你所需要的前端知識!

3.結果參數

傳入reject的參數,一般是字符串或Error實例,表示拋出的錯誤。

傳入resolve的參數,一般是相應的JSON數據等,表示得到的數據。

傳入resolve的參數,還可以是另一個Promise實例。

這時,只有當內層的Promise結束後,外層的Promise才會結束。

JS異步編程之Promise詳解,這才是你所需要的前端知識!

在這種情況下,如果內層失敗,並不等於傳遞Error實例給resolve不同。

前者是內層Promise拋出了錯誤將被外層捕獲,後者僅僅是參數為一個Error實例。

JS異步編程之Promise詳解,這才是你所需要的前端知識!

實例方法

then()

該方法可傳入兩個,分別對應成功/失敗時的回調函數。

該方法返回的是一個新的Promise對象,這也是可以使用鏈式(.then.then...)的原因。

JS異步編程之Promise詳解,這才是你所需要的前端知識!

1.return

鏈式中,後者的狀態取決於前者(成功/失敗)的回調函數中返回(return)的結果。

如果沒有返回,相當返回一個成功的狀態,值為undefined。

如果返回為Promise對象,後者的狀態由該對象的最終狀態決定。

如果返回為非Promise對象的數據,相當返回一個成功的狀態,值為此數據。

如果前者執行時拋出了錯誤,相當是返回一個失敗的狀態,值為此錯誤。

JS異步編程之Promise詳解,這才是你所需要的前端知識!

2.狀態的傳遞

在鏈式中,如果前者的狀態沒有被後者捕獲,會一直(像)冒泡到被捕獲為止。

狀態被捕獲後便消失,這之後的的狀態由當前then返回的狀態決定,之後重複。

依次打印出:

2 res 2000

3 res 3000

JS異步編程之Promise詳解,這才是你所需要的前端知識!

catch()

用於指定發生錯誤時的回調函數,等價於:.then(null, callback)。

其表現與then一致,比如返回新的Promise,狀態的繼承和傳遞等等。

一般推薦使用catch而不是then的第二個方法接收錯誤。

因為catch可以捕獲then自身的錯誤,也更接近同步的寫法(try/catch)。

new Promise(() => {})
.then(() => {
...
})
.catch(() => {
...
});

finally()

用於Promise處理結束後的收尾工作。

傳入其的回調函數不會接受任何參數,意味著沒有辦法知道Promise的結果。

這也正表明,finally裡面的操作與狀態無關,不依賴Promise的處理結果。

其本質和catch一樣,也是then方法的變種。

不過其僅僅是狀態的傳遞者,只會返回原狀態,不會接收狀態和創建新的狀態。

p.finally(() => {
// codes...
});

--- 等價於

p.then(res => {
// codes...
return res; // 將原成功狀態返回
}, err => {
// codes...
throw err; // 將原失敗狀態返回
});

示例

在請求數據時,我們會顯示加載圖案,請求完成後無論結果都要隱藏此圖案。

一般,一個完整的 Promise 的結構會如下。

showLoading = true;
new Promise((resolve, reject) => {
// 請求...
})
.then(res => {
// 成功處理...
})
.catch(err => {
// 失敗處理...
})
.finally(() => {
// 重置一些狀態...
showLoading = false;

});

靜態方法

resolve()

此方法直接返回一個狀態為resolved,值為其參數的Promise。

Promise.resolve(res);

--- 等價於

new Promise(resolve => resolve(res));

reject()

此方法直接返回一個狀態為rejected,值為其參數的Promise。

Promise.reject(res);

--- 等價於

new Promise((resolve, reject) => reject(res));

all()

此方法用於將多個Promise實例,包裝成一個新的Promise實例。

其參數為一個數組,每一項應為Promise實例(不是則會使用Promise.resolve進行轉化)。

新Promise的狀態取決於傳入數組中的每一項的最終狀態。

如果有一項狀態變成rejected,新實例則為rejected,值為該項的返回值。

如果全部項都變成了resolved,新實例則為resolved,值為包含每一項返回值的數組。

三秒後,打印出:[1, 2, 3]。

JS異步編程之Promise詳解,這才是你所需要的前端知識!

race()

此方法與all()基本相同,傳入的參數也是一個Promise數組。

不同的是,新Promise的最終狀態是由數組中第一個狀態改變的項(成功或失敗)決定的。

一秒後,打印出 1 。

JS異步編程之Promise詳解,這才是你所需要的前端知識!

編程是一種修行,我願與志同道合的朋友攜手前行,一起探索有關編程的奧妙!

如果您在前端學習的過程中遇到難題,歡迎【關注】並【私信】我,大家一起交流解決!

推薦文章:


分享到:


相關文章: