一個小小 Promise 選錯,差點把整套 API 打成 “502 修羅場”
開場先捅破一個幻覺
多個接口并發請求,看上去總是很酷:
- 一行
Promise.all - 三五個 API 一起飛
- 控制臺干干凈凈
- 工程師瞬間覺得自己像在寫“高并發微服務調度中心”
直到——一個小小的失敗,把整屏數據全帶崩。
很多前端都經歷過類似一幕:
const [user, orders, notifications] = await Promise.all([
getUser(),
getOrders(),
getNotifications(),
]);在一切順利的時候,這段代碼堪稱優雅。 直到其中某個接口開始“偶發抽風”,整個頁面就會瞬間告訴大家:
“全部完蛋了,別的成功也當不存在。”
真正把問題拉回現實的,是 Promise.all 和 Promise.allSettled 這對“兄弟”之間那一點點性格差異。
場景復盤:一個接口掉鏈子,全家一起陪葬
典型后臺看板場景:
getUser():拿用戶信息getOrders():拉訂單列表getNotifications():獲取通知
自然會寫出這樣一段并發邏輯:
const [user, orders, notifications] = await Promise.all([
getUser(),
getOrders(),
getNotifications(),
]);畫面一度很美:
- 網絡好 → 數據噌噌回來
- 微服務扛得住 → 所有模塊一次性就緒
直到有一天,其中一個微服務間歇性失敗:
- 某次超時
- 某次 500
- 某次返回完全不合規的數據導致 promise reject
結果是:
- 只要有一個 promise reject,整組
Promise.all直接拋錯 - 其他已經成功的數據一律當沒發生
- UI 沒有部分渲染、沒有兜底,只剩下一片錯誤的寂靜
這個時候,即便只是其中一個“小角落”接口掛了, 用戶看到的卻是——整個看板都“罷工”。
轉折點:換成 Promise.allSettled 之后,系統不再“一刀切”
Promise.allSettled 做的一件事情非常簡單:
不再“誰先失敗聽誰的”, 而是 “所有人先跑完,再匯報各自結果。”
對同一組請求,只要稍作改寫:
const results = await Promise.allSettled([
getUser(),
getOrders(),
getNotifications(),
]);
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('Success:', result.value);
} else {
console.warn('Failed:', result.reason);
}
});返回的不再是一個“解構即得結果”的數組,而是:
- 每一項都帶有
status字段:
'fulfilled':成功
'rejected':失敗
- 成功時有
value - 失敗時有
reason
這種結構,直接給了前端三個關鍵能力:
- 局部渲染
- 哪些模塊成功,就先渲染哪些模塊
- 某個接口掛了,也不會拖垮整個頁面
- 顯式標記失敗模塊
- 可以在 UI 上告訴用戶: “訂單模塊暫時加載失敗,稍后再試”
- 而不是一整屏空白
- 有選擇地重試
- 只針對 reject 的那部分數據走重試
- 不必把所有接口重新打一次
一句話:
從“要么全有,要么全無”, 變成了“誰靠譜先用誰,誰出問題單獨處理”。
應用的韌性和用戶體驗,都直接提了一檔。
行為差異,一張表說清
把兩者的核心行為對比一下:
1. Promise.all
- 策略:只要有一個 promise reject,整體立刻 reject
- 適合場景:
所有步驟缺一不可
任一失敗都意味著整個流程不能繼續
例如:
- 登錄流程:
校驗用戶 → 拉權限 → 拉偏好設置
其中任何一步失敗,登錄都不算成功
- 核心結算邏輯:
價格計算 / 優惠校驗 / 風控檢測
任一失敗,結果都不能給用戶
2. Promise.allSettled
- 策略:所有 promise 都跑完,無論成功與否,都一并返回結果
- 適合場景:
允許部分模塊出問題
失敗模塊可以降級、隱藏或延后
例如:
- 儀表盤 / 控制臺:
用戶信息、通知、推薦內容,某塊掛了不至于拖垮整頁
- 組件式首頁:
某個區塊的數據源暫時不可用,其它區塊依舊展示
簡化版總結:
Promise.all
- 偏“強一致”:要么都好,要么都崩
- 用在:業務上必須“全對”的流程
Promise.allSettled
- 偏“韌性”:給每一個 Promise 單獨結算
- 用在:可以容忍“部分缺失”的場景心智模型:先想好“這次能不能接受殘缺”
在選擇這兩個 API 的時候,有一個簡單但非常管用的問題:
“這一組操作里,能不能接受‘部分成功、部分失敗’?”
- 不能接受殘缺
例如認證、交易、排他性操作
只要有一步不對,整件事就必須 roll back / 宣告失敗
這時用 Promise.all 更符合業務語義
- 可以接受殘缺
例如儀表盤、信息聚合頁、多個 Widget 的組合
某個區塊失敗,其他區塊依然有價值
這時 Promise.allSettled 更接近真實需求
JavaScript 提供的是工具,真正做選擇的,是對業務容錯邊界足夠清醒的開發者。
總結
一次看似普通的 API 并發調用, 往往把“開發者如何看待失敗”暴露得一清二楚:
- 有人默認“要么全成,要么全掛”, 然后用
Promise.all把任何小錯誤都放大成全局崩潰; - 也有人承認世界就是偶爾會抖, 于是換成
Promise.allSettled,給失敗留出空間,也給用戶留下一點“還能用”的部分。
如果系統涉及:
- 多個微服務
- 不同可靠性的第三方 API
- 帶有 Widget 式布局的復雜頁面
那 Promise.allSettled 很可能就是那條把“線上小抖動”擋在災難之前的分界線。
工具從來都不缺, 真正拉開差距的,是每一次寫代碼前, 對“失敗會怎樣影響用戶”的那一點點多想半步。
























