網站一次載入最新文章、分類與推薦內容,推薦區塊出錯後,連已經取得的文章都沒有顯示?如果你把所有請求放進Promise.all(),並只在整組成功後更新畫面,任何一個Promise拒絕,都會讓整組進入錯誤分支。
幾個區塊可以各自顯示時,可用Promise.allSettled()收集每項結果,成功的照常顯示,失敗的留在自己的區塊提示。它不會把失敗變成功,也不會自動重試或取消請求。
先看Promise.all為何沒有交出整組結果
下面用虛構的Promise重現,不需要連到真實API。把程式貼進自己的空白測試頁開發者工具主控台;成功項目代表取得資料,失敗項目代表推薦服務出錯。
const tasks = [
Promise.resolve('文章已載入'),
Promise.reject(new Error('推薦服務失敗')),
Promise.resolve('分類已載入')
];
Promise.all(tasks).then(values => {
console.log('整組成功', values);
}).catch(error => {
console.log('整組失敗', error.message);
});
主控台會顯示「整組失敗 推薦服務失敗」,而不是執行上面的整組成功分支。這不代表另外兩項沒有完成;Promise.all()本身不會取消它們,只是這個寫法沒有在失敗分支使用它們的成功結果。
區塊彼此獨立,用allSettled逐項處理
改成下面的寫法,先檢查每項status,再讀取成功項的value或失敗項的reason。名稱與結果都按輸入順序配對,不能按哪個服務先回來猜區塊。
const names = ['文章', '推薦', '分類'];
const settledTasks = [
Promise.resolve('文章已載入'),
Promise.reject(new Error('推薦服務失敗')),
Promise.resolve('分類已載入')
];
Promise.allSettled(settledTasks).then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(names[index], result.value);
} else {
console.log(names[index], '暫時無法載入');
}
});
});
這次三行依序是「文章 文章已載入」、「推薦 暫時無法載入」與「分類 分類已載入」。推薦失敗的原因仍留在對應結果中;公開畫面使用簡短提示,維護紀錄再保存必要的錯誤類別,不要把含憑證或個人資料的完整錯誤直接印給訪客。
比較完成順序與結果順序
結果陣列依輸入順序排列,即使第一項比較慢也是如此。下面讓第二項先失敗,第一項晚一點成功,仍會先得到文章的結果,再得到推薦的結果。
const slowArticle = new Promise(resolve => {
setTimeout(() => resolve('文章'), 60);
});
const fastRecommendation = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('推薦失敗')), 10);
});
Promise.allSettled([
slowArticle, fastRecommendation
]).then(results => {
console.log(results.map(item => item.status));
// ['fulfilled', 'rejected']
});
這裡的10與60毫秒只是示範先後,不是網站的逾時設定。allSettled()要等所有項目完成或拒絕;若某項一直pending,這組也會一直等。若文章應該一取得就顯示,就應對各區塊分別更新畫面,而不是等整組收齊才畫出。
接真實fetch前,先定義什麼算成功
fulfilled只表示該Promise履行,不保證API回應符合業務要求。fetch()收到404等HTTP回應時,不一定拒絕;要先按需求檢查response.ok、解析內容與必要欄位,再把有效資料交給區塊。可參考fetch回傳404為何沒有進catch,將HTTP狀態判斷與整組結果處理分開。
若幾個步驟必須全部成功才能進行下一步,例如先取得會員權限再讀取受限資料,就不能把失敗步驟略過後照常顯示。allSettled()適合收集互不依賴的結果;它不提供交易回滾、權限判定、重試或逾時功能。先確認區塊之間是否真的獨立,再決定用整組成功條件還是逐項處理。

評論0