Promise 組合器

發佈於 · 標籤為 ECMAScript ES2020 ES2021

自 ES2015 引入 Promise 以來,JavaScript 僅支援兩種 Promise 組合器:靜態方法 Promise.allPromise.race

目前有兩個新提案正在標準化過程中:Promise.allSettledPromise.any。有了這些新增功能,JavaScript 中將共有四種 Promise 組合器,每種都能啟用不同的使用案例。

以下是四種組合器的概觀

名稱說明狀態
Promise.allSettled不會短路已新增至 ES2020 ✅
Promise.all當輸入值遭到拒絕時會短路已新增至 ES2015 ✅
Promise.race當輸入值已解決時會短路已新增至 ES2015 ✅
Promise.any當輸入值已完成時會短路已新增至 ES2021 ✅

讓我們來看每個組合器的範例使用案例。

Promise.all #

  • Chrome: 自版本 32 起支援
  • Firefox: 自版本 29 起支援
  • Safari: 自版本 8 起支援
  • Node.js: 自版本 0.12 起支援

Promise.all 讓您知道所有輸入 Promise 是否已完成,或其中一個遭到拒絕。

想像一下使用者按下按鈕,而您想要載入一些樣式表,以便您可以呈現全新的 UI。這個程式會同時針對每個樣式表啟動 HTTP 要求

const promises = [
fetch('/component-a.css'),
fetch('/component-b.css'),
fetch('/component-c.css'),
];
try {
const styleResponses = await Promise.all(promises);
enableStyles(styleResponses);
renderNewUi();
} catch (reason) {
displayError(reason);
}

您只想要在所有要求都成功後才開始呈現新的 UI。如果發生問題,您想要在不等待其他任何工作完成的情況下,盡快顯示錯誤訊息。

在這種情況下,您可以使用 Promise.all:您想要知道所有 Promise 何時完成,其中一個遭到拒絕時。

Promise.race #

  • Chrome: 自版本 32 起支援
  • Firefox: 自版本 29 起支援
  • Safari: 自版本 8 起支援
  • Node.js: 自版本 0.12 起支援

如果您想要執行多個 Promise,而且…

  1. 在第一個成功的結果出現時執行某些動作(如果其中一個 Promise 已完成),
  2. 在其中一個 Promise 遭到拒絕時立即執行某些動作。

也就是說,如果其中一個 Promise 遭到拒絕,您希望保留該拒絕以分別處理錯誤案例。以下範例執行確切的動作

try {
const result = await Promise.race([
performHeavyComputation(),
rejectAfterTimeout(2000),
]);
renderResult(result);
} catch (error) {
renderError(error);
}

我們啟動一項可能需要很長時間的計算密集型工作,但我們會與在 2 秒後遭到拒絕的 Promise 競爭。根據第一個完成或遭到拒絕的 Promise,我們會在兩個不同的程式碼路徑中呈現計算結果或錯誤訊息。

Promise.allSettled #

Promise.allSettled 會在所有輸入 Promise 完成時提供訊號,表示它們已完成遭到拒絕。這在您不關心 Promise 狀態,而只想知道工作是否完成的情況下很有用,無論是否成功。

例如,您可以啟動一系列獨立的 API 呼叫,並使用 Promise.allSettled 確保它們在執行其他動作(例如移除載入指示器)之前全部完成

const promises = [
fetch('/api-call-1'),
fetch('/api-call-2'),
fetch('/api-call-3'),
];
// Imagine some of these requests fail, and some succeed.

await Promise.allSettled(promises);
// All API calls have finished (either failed or succeeded).
removeLoadingIndicator();

Promise.any #

Promise.any 會在其中一個 Promise 完成時立即提供訊號。這類似於 Promise.race,但 any 其中一個 Promise 遭到拒絕時不會提早拒絕。

const promises = [
fetch('/endpoint-a').then(() => 'a'),
fetch('/endpoint-b').then(() => 'b'),
fetch('/endpoint-c').then(() => 'c'),
];
try {
const first = await Promise.any(promises);
// Any of the promises was fulfilled.
console.log(first);
// → e.g. 'b'
} catch (error) {
// All of the promises were rejected.
console.assert(error instanceof AggregateError);
// Log the rejection values:
console.log(error.errors);
// → [
// <TypeError: Failed to fetch /endpoint-a>,
// <TypeError: Failed to fetch /endpoint-b>,
// <TypeError: Failed to fetch /endpoint-c>
// ]
}

此程式碼範例會檢查哪個端點回應得最快,然後記錄它。只有在所有要求都失敗時,我們才會進入 catch 區塊,然後我們可以在其中處理錯誤。

Promise.any 的拒絕可以一次代表多個錯誤。為了在語言層級支援這一點,引入了稱為 AggregateError 的新錯誤類型。除了在上述範例中的基本用法之外,AggregateError 物件也可以像其他錯誤類型一樣以程式化方式建構

const aggregateError = new AggregateError([errorA, errorB, errorC], 'Stuff went wrong!');