【每日一面】對 Promise.race 的理解
基礎問答
問:有使用過 Promise.race 嗎,說說他的作用。
答:Promise.race 接收一個 Promise 數組(或者一個具有迭代器的對象)作為參數,返回一個新的Promise,這個新的 Promise 結果是數組中第一個狀態變更的Promise對象,無所謂這個狀態是否是成功(fulfilled)還是失敗(rejected)。后續Promise數組中的其他Promise對象狀態變更都不再關注。
擴展延伸
Promise 基礎
Promise 是 ES6 引入的異步編程解決方案,用于表示一個異步操作的最終完成(或失敗)及其結果值。它的核心價值是:
- 解決 “回調地獄”:將嵌套的回調邏輯轉為鏈式調用(then鏈),代碼更清晰;
- 統一異步操作接口:無論異步操作是 “成功” 還是 “失敗”,都通過統一的 Promise 對象處理,避免回調函數分拆(如成功回調、失敗回調分離)。
Promise 有且僅有三種狀態,且狀態變化是單向不可逆的,這三種狀態分別是:
- pending(等待態):初始狀態,異步操作未完成;
- fulfilled(成功態):異步操作完成,Promise 狀態從pending轉為fulfilled,并攜帶成功結果(value);
- rejected(失敗態):異步操作失敗,Promise 狀態從pending轉為rejected,并攜帶失敗原因(reason)。
這是 Promise 的核心特性,也是所有方法設計的基礎。
狀態變化規則:
- 只能從pending轉為fulfilled,或從pending轉為rejected;
- 一旦狀態轉為fulfilled或rejected,就會 “凝固”,后續無法再改變狀態;
- 狀態變化時,會觸發對應的回調函數(then的成功回調、catch的失敗回調)。
Promise 的核心實例方法:
- then 方法:處理成功 / 失敗結果
- catch 方法:專門處理失敗結果,catch(onRejected) 等價于then(null, onRejected),是處理rejected狀態的語法糖;
- finally 方法:無論成功失敗都執行
Promise靜態方法
除了Promise.race,Promise 還有Promise.all/Promise.allSettled/Promise.resolve/Promise.reject 等靜態方法,差異點見下表:
| 方法 | 核心作用 | 狀態觸發條件 | 返回值格式 | 適用場景 |
|---|---|---|---|---|
| Promise.race | 多個 Promise 競爭,取第一個完成的結果 | 任意一個 Promise 改變狀態(fulfilled/rejected),立即觸發對應狀態 | 單個值(第一個 Promise 的 value/reason) | 超時控制、請求降級 |
| Promise.all | 等待所有 Promise 成功,取全部結果 | 所有 Promise 都 fulfilled,才觸發 fulfilled;任意一個 rejected,立即觸發 rejected | 數組(按原數組順序排列的所有 value) | 并行請求多個無依賴接口(如加載頁面資源) |
| Promise.allSettled | 等待所有 Promise 完成,取全部結果(無論成敗) | 所有 Promise 都改變狀態(fulfilled/rejected),才觸發 fulfilled | 數組(每個元素含 status 和 value/reason) | 需知道所有請求結果(如批量操作日志) |
| Promise.resolve | 快速創建一個 fulfilled 狀態的 Promise | 無(直接返回 fulfilled 狀態的 Promise) | 單個 value(參數值,或參數 Promise 的 value) | 統一 Promise 格式、轉換同步值為異步 |
| Promise.reject | 快速創建一個 rejected 狀態的 Promise | 無(直接返回 rejected 狀態的 Promise) | 單個 reason(參數值) | 快速拋出異步錯誤 |
與 async/await 的關系
async/await 本質是 Promise 的語法糖。
- async 函數:修飾的函數返回值必然是 Promise(若 return 非 Promise 值,會用Promise.resolve()包裝)
- await 關鍵字:本質是 “等待 Promise 狀態變化” 的語法糖,只能在 async 函數內部使用,后面跟 Promise 對象,會暫停 async 函數執行,直到 Promise 狀態轉為fulfilled,并將value作為 await 表達式的結果,如果 Promise 狀態轉為rejected,會拋出錯誤,需用try/catch捕獲(等價于 Promise 的catch)。
面試追問
-
知道定義,能手寫一個 Promise.race 方法嗎?
/** * 手寫Promise.race * @param {Iterable} iterable - 可迭代對象(如數組) * @returns {Promise} - 新的Promise對象 */ function myPromiseRace(iterable) { // 1. 邊界處理:參數必須是可迭代對象(檢查是否有Symbol.iterator方法) if (typeof iterable[Symbol.iterator] !== 'function') { return new Promise((_, reject) => { reject(new TypeError('Promise.race() 參數必須是可迭代對象')); }); } // 2. 返回新Promise return new Promise((resolve, reject) => { // 3. 遍歷可迭代對象(用for...of兼容所有可迭代對象) for (const item of iterable) { // 4. 用Promise.resolve包裝item,處理非Promise元素 Promise.resolve(item) .then((value) => { // 一旦有元素成功,立即resolve新Promise(后續元素不再處理) resolve(value); }) .catch((reason) => { // 一旦有元素失敗,立即reject新Promise(后續元素不再處理) reject(reason); }); } }); } -
Promise 還有什么方法?有什么差異?
參考擴展延伸部分 -
一個 Promise 可以多次 resolve 嗎?
可以多次執行 resolve 或 reject,但是 Promise 的狀態只會改變一次,就是第一次執行 resolve的時候,后續雖然會執行 resolve,但是不影響狀態,沒有作用。 -
用 Promise.race 實現超時控制時,若超時后原請求仍在繼續,會有什么問題?如何解決?
出現資源浪費,Promise.race 僅感知超時并返回結果,但是原請求依舊會繼續執行,可以結合axios的CancelToken或AbortController取消請求去中斷。 -
如何用 Promise 實現‘重試機制’?比如接口請求失敗后,重試 3 次,每次間隔 2 秒。
/** * Promise重試機制 * @param {Function} requestFn - 請求函數(返回Promise) * @param {number} maxRetry - 最大重試次數 * @param {number} interval - 重試間隔(毫秒) * @param {number} currentRetry - 當前重試計數(默認0,內部使用) * @returns {Promise} - 最終請求結果 */ function promiseRetry(requestFn, maxRetry = 3, interval = 2000, currentRetry = 0) { return new Promise((resolve, reject) => { requestFn() .then(resolve) // 請求成功,直接返回結果 .catch((err) => { // 若已達最大重試次數,拋出最終錯誤 if (currentRetry >= maxRetry) { reject(new Error(`重試${maxRetry}次后仍失敗:${err.message}`)); return; } // 未達最大次數,延遲后重試 console.log(`請求失敗,${interval}ms后重試(第${currentRetry+1}次)`); setTimeout(() => { // 遞歸調用,當前重試計數+1 promiseRetry(requestFn, maxRetry, interval, currentRetry + 1) .then(resolve) .catch(reject); }, interval); }); }); } // 調用示例:請求失敗后重試3次,每次間隔2秒 const fetchData = () => { // 模擬接口請求(50%概率失敗) return new Promise((resolve, reject) => { setTimeout(() => { if (Math.random() > 0.5) { resolve('請求成功結果'); } else { reject(new Error('接口返回錯誤')); } }, 1000); }); }; promiseRetry(fetchData, 3, 2000) .then((res) => console.log('最終結果:', res)) .catch((err) => console.log('最終失敗:', err.message));


浙公網安備 33010602011771號