「 譯」Promise.all的深入理解以及與Promise.allSettled的比較

2021-02-16 前端探索之路
閱讀本文能收穫什麼?

在本文中,我們將介紹Promise構造函數上可用的2個靜態方法:Promise.all和Promise.allSettled。

我們將探究它們功能之間的區別,甚至我們能夠創建自己的polyfill 。

這將使我們對這些方法以及Promises在JavaScript中有深入的了解。

我們將使用簡單的示例,您將可以輕鬆地自己複製它們。因此,建議您使用某種在線編輯器運行示例繼續閱讀本文,例如:repl。


Promise.all

隨著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

相關焦點

  • Promise 中的三兄弟 .all(), .race(), .allSettled()
    Promise.all()Promise.all()的類型籤名:返回情況:完成(Fulfillment):如果傳入的可迭代對象為空,Promise.all 會同步地返回一個已完成(resolved)狀態的promise。
  • 每日一題之手寫Promise.all和Promise.race
    「手寫源碼題是近兩年面試常見的題目,比如對於Promise來說,可能面試官就會讓你手寫Promise.all或者Promise.race, 你能寫出來嗎
  • 今日一題 - 請模擬實現一個Promise.all() 方法?
    對Promise.all 的理解Promise.all()方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。Promise.all()方法的參數可以不是數組,但必須具有 Iterator 接口(所以數組、Map、Set都可以),並且只返回一個Promise實例,輸入的所有promise的resolve回調的結果會按傳入的按順序作為一個數組的其中一項返回。
  • 所有你需要了解的Promise.all
    什麼是Promise.all?Promise.all實際上是一個承諾,它將一系列承諾作為輸入(可迭代)。然後,當所有承諾得到解決或其中任何一個被拒絕時,它就會得到解決。例如,假設你有十個承諾(執行網絡調用或資料庫連接的異步操作)。
  • 如何使用Promise.race() 和 Promise.any() ?
    分別是: Pending:你不知道你是否能得到那部手機 Fulfilled:老媽高興了,給你買了 Rejected:老娘不開森了,不給你買了這個是我目前聽到,最快能理解 Promise 事例。如果你還沒有開始學習 Promise ,建議你這樣做。
  • 初學者應該看的JavaScript Promise 完整指南
    這篇文章算是 JavaScript Promises 比較全面的教程,該文介紹了必要的方法,例如 then,catch和finally。此外,還包括處理更複雜的情況,例如與Promise.all並行執行Promise,通過Promise.race 來處理請求超時的情況,Promise 鏈以及一些最佳實踐和常見的陷阱。
  • 理解異步之美--- Promise與async await(一)
    當你把一件事情交給promise時,它的狀態就是Pending,任務完成了狀態就變成了Resolved、沒有完成失敗了就變成了Rejected。Promise.all 與 Promise.race的妙用Promise.all 接收一個數組,數組的每一項都是一個promise對象。
  • 你真的懂Promise嗎
    事實上,有些朋友對於這個幾乎每天都在打交道的「老朋友」,貌似全懂,但稍加深入就可能疑問百出,本文帶大家深入理解這個熟悉的陌生人—— Promise.不管宏任務是否到達時間,以及放置的先後順序,每次主線程執行棧為空的時候,引擎會優先處理微任務隊列,處理完微任務隊列裡的所有任務,再去處理宏任務。
  • 我終於弄懂了Promise
    以上是原文解釋,我們可以理解成p2.then 實際上是p1.thenPromise.prototype.then()then方法是定義在原型對象Promise.prototype上的,同時then方法的兩個參數都是非必選的。
  • JavaScript Promise啟示錄
    近幾年隨著JavaScript開發模式的逐漸成熟,CommonJS規範順勢而生,其中就包括提出了Promise規範,Promise完全改變了js異步編程的寫法,讓異步編程變得十分的易於理解。所謂Promise,字面上可以理解為「承諾」,就是說A調用B,B返回一個「承諾」給A,然後A就可以在寫計劃的時候這麼寫:當B返回結果給我的時候,A執行方案S1,反之如果B因為什麼原因沒有給到A想要的結果,那麼A執行應急方案S2,這樣一來,所有的潛在風險都在A的可控範圍之內了。
  • 面試官:你是怎麼理解ES6中 Promise的?使用場景?
    一、介紹Promise,譯為承諾,是異步編程的一種解決方案,比傳統的解決方案(回調函數)更加合理和更加強大在以往我們如果處理多層異步操作,我們往往會像下面那樣編寫我們的代碼doSomething(function(result
  • 簡單記錄下Promise的使用
    ,當我們在需要多個異步請求都執行完再執行下一步操作時,可以考慮使用promise。這裡只簡單記錄下promise的使用。的all函數使用:隨便一個請求隨機數小於5,終將直接進入promise.all的catch只有當三個請求隨機生成的數字都大於5時才能進入promise.all的then裡邊,返回的結果是一個數組,是a,b,c請求resolve的結果(成功)。
  • 壓箱底筆記:Promise和Async/await的理解和使用
    2.1 理解2.2 Promise的狀態改變2.3 Promise基本流程2.4 Promise的基本使用3. 為什麼要用Promise?3.1 指定回調函數的方式更加靈活3.2 支持鏈式調用,可以解決回調地獄問題4.
  • 淺談NodeJS之Promise
    Promise是一個構造函數,方法有all、reject、resolve這幾個,原型上有then、catch等方法。那麼new Promise 出來的對象肯定就有then、catch方法。跟著我一步一步學習吧。本文能讓你對Promise有一個顛覆的認識。
  • JavaScript異步與Promise實現
    本篇將介紹Promise,讀完你應該了解什麼是Promise,為什麼使用Promise,而不是回調函數,Promise怎麼使用,使用Promise需要注意什麼,以及Promise的簡單實現。為了更好的理解Promise,我們介紹一下Promises/A+,一個公開的可操作的Promises實現標準。先介紹標準規範,再去分析具體實現,更有益於理解。Promise代表一個異步計算的最終結果。使用promise最基礎的方式是使用它的then方法,該方法會註冊兩個回調函數,一個接收promise完成的最終值,一個接收promise被拒絕的原因。
  • 理解異步之美——Promise與async await(二)
    1:對promise實例定義一個狀態,值為PENDING。2:給promise實例定義一個存放值的空間。3:設置一個發布列表,在以後的指定時間發布其中的事件。自己也可以嘗試的去實現一下符合promise規範的promise功能。親! 學習完要思考不知道看到這裡大家對網上常見的promise源碼實現有一種什麼樣的感覺???
  • JavaScript之Promise對象
    用法:newPromise(function(resolve, reject){...}).then(function(res){}).catach(function(err){}).finally(function(){});Promise.all:將多個Promise實例包裝成一個新的Promise
  • JS專題系列之Promise的原理及實現
    promise有三種狀態: pending(等待態),fulfiled(成功態),rejected(失敗態);狀態一旦改變,就不會再變。創造promise實例後,它會立即執行。promise解決的問題回調地獄,代碼難以維護, 常常第一個的函數的輸出是第二個函數的輸入這種現象promise可以支持多個並發的請求,獲取並發請求中的數據這個promise可以解決異步的問題,本身不能說promise是異步的二、分析Promise如果我們想要封裝promise就需要考慮以下幾個問題
  • JavaScript 異步與 Promise 實現
    本篇將介紹Promise,讀完你應該了解什麼是Promise,為什麼使用Promise,而不是回調函數,Promise怎麼使用,使用Promise需要注意什麼,以及Promise的簡單實現。為了更好的理解Promise,我們介紹一下Promises/A+,一個公開的可操作的Promises實現標準。先介紹標準規範,再去分析具體實現,更有益於理解。Promise代表一個異步計算的最終結果。使用promise最基礎的方式是使用它的 then方法,該方法會註冊兩個回調函數,一個接收promise完成的最終值,一個接收promise被拒絕的原因。
  • ES6之Promise的使用
    Promise 對象用於表示一個異步操作的最終狀態(完成或失敗),以及其返回的值。方法Promise.all(iterable)這個方法返回一個新的promise對象,該promise對象在iterable參數對象裡所有的promise對象都成功的時候才會觸發成功,一旦有任何一個iterable裡面的promise對象失敗則立即觸發該promise