在本文中,我們將介紹Promise構造函數上可用的2個靜態方法:Promise.all和Promise.allSettled。
我們將探究它們功能之間的區別,甚至我們能夠創建自己的polyfill 。
這將使我們對這些方法以及Promises在JavaScript中有深入的了解。
我們將使用簡單的示例,您將可以輕鬆地自己複製它們。因此,建議您使用某種在線編輯器運行示例繼續閱讀本文,例如:repl。
隨著ES6中原生Promise的引入,我們將接受收到了一個靜態Promise.all方法。這是在JavaScript中同時執行異步任務的最基本方法之一。
它的基本用法和行為非常簡單。您將一系列的Promises傳遞給它,然後它等待所有這些Promises得到解決。在此之後,您將從所有各自的Promise中收到一系列結果。
假設我們有兩個Promises和一個異步函數。第一個Promise解析為數字,第二個解析為字符串,異步函數將返回的Promise將解析為一個布爾值:
const firstPromise = Promise.resolve(3);const secondPromise = new Promise(resolve => resolve('three'));const createThirdPromise = async () => true;我們使用了一個異步函數而不是第三個Promise來向您證明,它們都是返回Promises的函數(您可以通過閱讀 async/await and Promise interoperability)。
我們還使用了兩種不同的方法來創建Promise,該Promise立即解析為選定的值-使用Promise.resolve方法,並且僅使用Promise構造函數。
這裡要記住的重要一點是:兩個第一個Promise和async函數之間存在嚴重差異。這兩個Promise已經存在並且正在執行。例如,如果它們表示HTTP請求,那麼這些請求現在已經在代碼中執行了。
同時,在使用異步函數的情況下,尚未執行任何操作-解析為布爾值的Promise甚至還不存在!我們將在將其傳遞到Promise.all期望的數組之前創建它。
我們強調這些點,因為一個普遍的誤解是Promise.all以某種方式開始執行傳遞給它的Promises。但這種情況並非如此。到數組中將Promises提供給Promise.all時,它們都已經被執行了。(您可以通過閱讀 3 biggest mistakes made when using Promises)。
因此Promise.all不會開始Promises執行,而只是等待它們完成。如果所有Promises都已被較早地解決(例如,如果所有HTTP請求都已經完成),則Promise.all將幾乎立即解析為一個值,因為根本沒有任何可等待的。
為了說明這一點,請看一下我們如何調用Promise.all方法:
Promise.all([ firstPromise, secondPromise, createThirdPromise()]);第一個和第二個Promise已經存在,因此我們只需將它們傳遞給數組。但是由於Promise.all期望該數組中的Promises,而不是函數(即使它們是異步的!),因此我們需要執行異步函數,然後將其結果傳遞給數組。
因此,您可以看到,當Promise.all收到Promises時,它們都已經在執行中。也許其中一些甚至已經解決!
現在我們可以使用所有Promise的結果數組,當所有Promise都得到解決時,這些結果將提供給我們:
Promise.all([ ]) .then(([a, b, c]) => console.log(a, b, c));請注意,我們使用數組解構來從三個相應的Promises中獲得三個結果。數組中結果的順序與將Promises傳遞給Promise.all的順序匹配。
您可能現在已經知道,Promise.all本身會返回一個Promise。多虧了async / await和Promise的互操作性,我們仍然可以使用方便的數組解構,以更好的方式檢索結果:
const [a, b, c] = await Promise.all([ ]);console.log(a, b, c);這看起來更簡潔,但是只有當該代碼在異步函數中或者編程環境支持頂級等待時,它才有效。
Promise.all錯誤處理和Promise.allSettled我們介紹了Promise.all的基本用法。現在讓我們看一下當傳遞給它的一個Promise引發錯誤時它的行為。這將幫助我們理解為什麼在2019年引入Promise.allSettled這個靜態方法。
讓我們修改前面的示例,以便其中一個Promises導致錯誤:
const firstPromise = Promise.resolve(3);const secondPromise = Promise.reject('Some error message');const createThirdPromise = async () => true;您可以看到現在第二個Promise將導致錯誤,因為我們使用了reject方法而不是resolve方法。
讓我們在Promise.all用法示例中添加一個錯誤處理功能:
Promise.all([ ]) .then( ([a, b, c]) => console.log(a, b, c), err => console.log(err) );運行此代碼後,我們只會看到一些錯誤消息記錄到控制臺。
發生了什麼?好吧,因為其中一個Promise引發了錯誤,所以Promise.all也會簡單地重新拋出該錯誤,即使所有其他Promises都已成功解決。
也許您已經發現該方法存在問題。即使三個Promise中有兩個沒有失敗,我們仍然不能以任何方式使用它們的結果,僅僅是因為其中一個承諾拋出了錯誤。
這是因為Promises總是以兩種狀態之一(已解決或已拒絕(與「拋出錯誤」完全相同))結束,並且兩者之間沒有任何關係。
從此處的Promise.all方法返回的Promise也是如此-傳遞給該方法的所有Promise成功解析並且輸出Promise得到解析,或者(至少一個)Promises拒絕,我們的Promise輸出也立即拒絕,不在乎其他(也許是成功的)Promises的返回值。
那麼,有什麼方法可以從正確解決的Promise中恢復那些「缺失」的價值?讓我們嘗試做到這一點。
我們可以做的是嘗試處理Promise中的錯誤,我們知道該錯誤將拋出並作為新值返回已拋出的錯誤對象(在我們的情況下為字符串):
Promise.all([ firstPromise, secondPromise.catch(error => error), createThirdPromise()]);請注意,我們如何使用catch方法和箭頭函數來檢索拋出的錯誤對象,並立即再次將其返回,以使其成為Promise的新「success」值。新的Promise不會再失敗-錯誤已得到解決,並且此Promise可以正確解析為一個值。因此,對於Promise.all方法,它不再是失敗的Promise。
這樣,即使第二個Promise拋出錯誤,我們仍然會從第一個和第三個Promises中接收值。此外,我們會收到它拋出的錯誤(「一些錯誤消息」字符串),而不是第二個Promise的值,因此我們可以基於該值處理錯誤。
但是顯然,在實際的應用程式中,我們並不真正知道哪個Promises將失敗,因此我們需要處理所有這些潛在的錯誤:
const promises = [ firstPromise, secondPromise, createThirdPromise()]const mappedPromises = promises.map( promise => promise.catch(error => error));Promise.all(mappedPromises) .then(([a, b, c]) => console.log(a, b, c));這裡我們做的和以前完全一樣,但是我們使用map方法在所有Promises上做。然後,我們在已處理錯誤的mapedPromises上調用Promise.all,而不是Promise可能失敗的原始promise數組。
現在,運行此示例將以 Some Error message 記錄到控制臺結束。
但是問題出現了。進行此更改之後,我們如何知道列印到控制臺的值是由於正確解決了Promise還是由於使用catch處理的錯誤導致的?事實證明,我們不能:
Promise.all(mappedPromises) .then(([a, b, c]) => { });因此,為了解決該問題,我們需要使代碼複雜一些。
與其直接從Promises中返回一個值,不如將每個值包裝在一個具有特殊標誌的對象中。該標誌將告訴我們該值是來自已解決(或有時也稱為「已實現」)的Promise還是來自被拒絕的值:
promise.then( value => ({ status: 'fulfilled', value }), reason => ({ status: 'rejected', reason }))您會看到,如果此Promise解析為一個值,它將返回一個對象,該對象的標誌已實現且該值本身位於屬性值之下。
如果Promise拋出,它將返回一個對象,該對象的標誌被拒絕,並且錯誤對象本身在屬性原因下。
請注意,此新構造的Promise絕不會引發錯誤,換句話說,它永遠不會進入拒絕狀態。它始終解析為一個值,但是此值是一個對象,它告訴我們原始Promise真正發生了什麼-無論是解析還是拒絕。
現在,我們可以將此代碼應用於傳遞給Promise.all的每個Promise:
const promises = [ firstPromise, secondPromise, createThirdPromise()]const mappedPromises = promises.map(promise => promise.then( value => ({ status: 'fulfilled', value }), reason => ({ status: 'rejected', reason }) ));Promise.all(mappedPromises);現在運行Promise.all函數並將結果記錄到控制臺:
Promise.all(mappedPromises) .then(([a, b, c]) => { console.log(a); console.log(b); console.log(c); });運行代碼後,您將看到以下輸出:
{ status: 'fulfilled', value: 3 }{ status: 'rejected', reason: 'Some error message' }{ status: 'fulfilled', value: true }這正是我們想要的!
即使某些Promise失敗了(就像第二個Promise一樣),我們仍然從正確解析的Promise中獲取值。
我們還從失敗的承諾中收到錯誤消息,以便我們可以處理必要的那些錯誤。
此外,通過讀取status屬性,我們可以輕鬆分辨出哪些價值來自已實現的Promise,哪些價值來自已拒絕的Promise。
下面我們通過Promise.allSettled來試試:
它的工作原理與我們上面詳述的代碼完全相同,但它為您完成了所有工作。通過將以下代碼添加到我們的代碼段中,您可以看到這一點:
Promise.all(mappedPromises) .then(([a, b, c]) => { console.log(a); console.log(b); console.log(c); console.log('\n'); }) .then(() => Promise.allSettled(promises)) .then(([a, b, c]) => { console.log(a); console.log(b); console.log(c); });因此,我們首先運行Promise.all(mappedPromises),在此我們手動進行錯誤處理。我們將結果記錄到控制臺,並記錄換行符\ n以便在控制臺中留出一個空格,以便我們可以更清楚地看到兩種單獨方法的結果。
然後我們運行Promise.allSettled(promises)。請注意,我們在原始的Promise數組上運行它,而不是mappedPromises。這是因為allSettled將為我們完成所有錯誤處理-這就是該方法的重點。因此,我們只需向其傳遞一系列原始承諾即可,而無需擔心其他任何事情。
最後,我們只記錄Promise.allSettled的結果,以將它們與Promise.all的結果進行比較。
在運行該代碼之前,請確保您處於支持allSettled的環境中。畢竟,這是一個相當新的功能。您可以在這裡查看支持。
運行代碼後,您將看到-確實-這兩種方法的行為相同,並且具有完全相同的輸出:
{ status: 'fulfilled', value: 3 }{ status: 'rejected', reason: 'Some error message' }{ status: 'fulfilled', value: true }{ status: 'fulfilled', value: 3 }{ status: 'rejected', reason: 'Some error message' }{ status: 'fulfilled', value: true }請注意,我們基本上為Promise.allSettled創建了一個polyfill。作為練習,您可以嘗試將我們的代碼包裝到功能類似於allSettled的allSettledPolyfill函數中,並在其他示例上對其進行測試。
在3個以上Promises中使用時,它們的行為是否相同?當更多的Promises同時失敗時,它們的行為是否相同?將空數組傳遞給它們兩個是否會以相同的結果結束?自己試試吧!
Promise.all vs Promise.allSettled-總結我們深入解釋了Promise.all的工作原理。然後,我們介紹了某些有時不受歡迎的特徵。這些特徵是創建新方法Promise.allSettled的動機,我們可以從頭開始編寫自己的方法。
讓我們通過簡要總結這兩種方法之間的主要區別來結束本文:
Promise.all接受一個Promise數組,並返回一個Promise解析為來自所有各個Promises的值數組。Promise.allSettled接受相同的輸入,但它解析為的數組存儲包裝返回值而不是值本身的對象。
如果傳遞給Promise.all的任何Promise都引發錯誤,Promise.all將停止等待其他Promise,並立即重新引發相同的錯誤。另一方面,Promise.allSettled永遠不會引發錯誤。如果某些Promise失敗,它仍然等待所有其他Promise解析或拒絕,然後簡單地在為該Promise返回的對象上用被拒絕標誌標記失敗的Promise。
就是這樣!我希望本文能使您對這兩種方法有深入的了解。Promise.all 與 Promise.allSettled都有自己的使用場景,決定選擇哪個始終取決於您如何處理失敗的Promise。
原文連結🔗:https://dev.to/mpodlasin/an-in-depth-explanation-of-promise-all-and-comparison-with-promise-allsettled-2olo