Promise.all的時(shí)代終結(jié)了,新方法即將取代他
前言
大家好,我是林三心,用最通俗易懂的話講最難的知識點(diǎn)是我的座右銘,基礎(chǔ)是進(jìn)階的前提是我的初心~
為什么 Promise.all 不安全?并發(fā)處理的新選擇 Promise.allSettled
在現(xiàn)代前端開發(fā)中,異步編程是繞不開的話題。Promise.all 作為處理多個(gè)并發(fā)異步操作的利器,因其“快”而備受青睞——它能讓所有任務(wù)同時(shí)發(fā)起,等待全部成功,從而大幅提升頁面數(shù)據(jù)加載效率。
然而,這份“快”是有代價(jià)的,它背后隱藏著一個(gè)巨大的陷阱:脆弱性。換句話說,Promise.all 其實(shí)并不“安全”。
一、 Promise.all 的“致命傷”:一個(gè)失敗,全盤皆輸
Promise.all 的工作機(jī)制遵循“全部或無一”的原則。它接收一個(gè) Promise 數(shù)組,并返回一個(gè)新的 Promise。其行為是:
- 全部成功(Fulfilled):當(dāng)所有輸入的 Promise 都成功時(shí),返回的 Promise 才會成功,結(jié)果是一個(gè)包含所有成功結(jié)果的數(shù)組。
- 一個(gè)失敗(Rejected):只要數(shù)組中任何一個(gè) Promise 失敗(rejected),返回的 Promise 就會立即失敗,并拋出第一個(gè)失敗的原因。
這就是它最大的問題。讓我們看一個(gè)例子:
// 假設(shè)有三個(gè)異步請求:獲取用戶信息、獲取商品列表、獲取消息通知
const fetchUserInfo = fetch('/api/user');
const fetchProducts = fetch('/api/products');
const fetchNotifications = fetch('/api/notifications');
Promise.all([fetchUserInfo, fetchProducts, fetchNotifications])
.then(([userInfo, products, notifications]) => {
// 只有當(dāng)三個(gè)請求都成功時(shí),才會進(jìn)入這里
renderUserPage(userInfo, products, notifications);
})
.catch(error => {
// 如果任何一個(gè)請求失敗,就會跳到這里
console.error('有一個(gè)請求失敗了:', error);
showErrorPage('頁面加載失敗,請重試!');
});場景分析: 如果 fetchNotifications(獲取通知)的接口掛了,返回了 500 錯(cuò)誤,那么即使 fetchUserInfo 和 fetchProducts 已經(jīng)成功請求回來了數(shù)據(jù),Promise.all 也會立刻終止,直接跳入 .catch 分支。
結(jié)果就是:用戶看到了一個(gè)全屏的錯(cuò)誤提示,本已成功加載的用戶信息和商品列表也無法展示給用戶,體驗(yàn)極差。
二、更安全的新選擇:Promise.allSettled
為了解決 Promise.all 的這個(gè)痛點(diǎn),ES2020 引入了 Promise.allSettled 方法。它的行為更加寬容和穩(wěn)健:
- 等待所有:它會等待所有輸入的 Promise 都“敲定”(settled),即無論是成功(fulfilled)還是失敗(rejected)。
- 永不失敗:**
Promise.allSettled自身永遠(yuǎn)不會被 reject**,它總是會成功返回一個(gè)數(shù)組。 - 詳情可知:返回的數(shù)組中的每個(gè)對象都包含了每個(gè) Promise 的最終狀態(tài)和結(jié)果(或原因)。
每個(gè)結(jié)果對象都有兩種形態(tài):
// 成功狀態(tài)
{ status: 'fulfilled', value: /* 成功的結(jié)果 */ }
// 失敗狀態(tài)
{ status: 'rejected', reason: /* 失敗的原因(錯(cuò)誤對象)*/ }三、如何使用 Promise.allSettled?
我們用 Promise.allSettled 重構(gòu)上面的例子:
Promise.allSettled([fetchUserInfo, fetchProducts, fetchNotifications])
.then((results) => { // 注意:這里永遠(yuǎn)不會 catch
// results 是一個(gè)包含三個(gè)對象的數(shù)組
const userInfo = results[0].status === 'fulfilled' ? results[0].value : null;
const products = results[1].status === 'fulfilled' ? results[1].value : null;
const notifications = results[2].status === 'fulfilled' ? results[2].value : null;
// 我們可以針對每個(gè)結(jié)果進(jìn)行精細(xì)化處理
if (userInfo && products) {
// 只要核心數(shù)據(jù)(用戶和商品)還在,就渲染頁面
renderUserPage(userInfo, products, notifications); // notifications 可能是 null
if (!notifications) {
showToast('通知獲取失敗,不影響主要功能');
}
} else {
// 如果核心數(shù)據(jù)缺失,再顯示錯(cuò)誤頁
showErrorPage('核心數(shù)據(jù)加載失敗');
}
});
// 不需要 .catch,因?yàn)樗肋h(yuǎn)不會被觸發(fā)重構(gòu)后的優(yōu)勢:
- 體驗(yàn)提升:即使通知接口掛了,用戶依然能看到頁面主體內(nèi)容,只會收到一個(gè)輕量的提示。
- 韌性增強(qiáng):單個(gè)接口的失敗不會導(dǎo)致整個(gè)頁面或功能的崩潰。
- 信息完整:我們可以確切地知道每個(gè)任務(wù)的執(zhí)行結(jié)果,并據(jù)此做出更細(xì)致的 UI 響應(yīng)。
四、如何選擇?Promise.all vs Promise.allSettled
特性 | Promise.all | Promise.allSettled |
核心原則 | 全部或無一(All-or-nothing) | 有始有終(Complete-all) |
最終狀態(tài) | 如果有一個(gè)失敗,則變?yōu)?Rejected | 永遠(yuǎn)變?yōu)?Fulfilled |
結(jié)果 | 所有成功的結(jié)果的數(shù)組 | 描述每個(gè) Promise 結(jié)果的對象數(shù)組 |
適用場景 | 任務(wù)強(qiáng)依賴 :多個(gè)任務(wù)必須全部成功才能進(jìn)行下一步(例如:下單需要同時(shí)校驗(yàn)庫存、優(yōu)惠券、地址都有效) | 任務(wù)弱依賴 :需要知道每個(gè)任務(wù)的最終狀態(tài),即使某些失敗也不影響整體(例如:批量操作、頁面初始化加載多個(gè)獨(dú)立模塊、上報(bào)日志) |
總結(jié)
Promise.all 并非“不安全”,而是它苛刻的成功條件在很多場景下顯得過于脆弱。它是一把鋒利的快刀,但在需要韌性的場合很容易折斷。
而 Promise.allSettled 提供了一種更具彈性的并發(fā)處理模式,它允許我們接受部分失敗,并基于完整的結(jié)果信息做出更優(yōu)雅的降級處理,從而極大提升應(yīng)用的健壯性和用戶體驗(yàn)。
當(dāng)下次你需要處理多個(gè)并發(fā)請求時(shí),不妨先思考一下:這些任務(wù)之間的關(guān)系是強(qiáng)依賴還是弱依賴?如果答案是后者,那么 Promise.allSettled 就是你更安全、更現(xiàn)代的選擇。





























