具代表性的面試主題

前端面試題:如何用 AbortSignal.timeout 與 any 取消請求?

前端中等
Offer.cc 編輯團隊發佈 更新

題幹

一個搜尋頁需要在使用者離開、請求逾時或元件卸載時取消 fetch。請比較 AbortSignal.timeout、AbortSignal.any 和 AbortController,解釋 TimeoutError 與 AbortError,並給出相容與驗證方案。

題幹與適用場景

搜尋頁會並發請求建議、畫像和結果介面。使用者輸入新關鍵字或離開頁面時,舊請求應盡快取消;網路長時間無回應時則應逾時,但頁面從背景恢復後不能把凍結時間誤算成請求耗時。請用 AbortSignal.timeout()AbortSignal.any()AbortController 設計取消策略。

這道題適合前端、Web 效能和非同步架構職位。重點是區分使用者取消、逾時、頁面生命週期和真實網路失敗,避免把所有異常都顯示成「請求逾時」。

面試官考察點

強回答會指出 AbortSignal.timeout() 返回自動中止的訊號,逾時原因是 TimeoutError;使用者或控制器主動中止通常是 AbortError。還應說明逾時基於活動時間,頁面進入 bfcache 或 Worker 暫停時計時會暫停;多個訊號可透過 AbortSignal.any() 合併,並保持可診斷的原因。

回答前需要釐清的問題

  • 哪些請求可以安全取消,寫入操作是否需要完成或查詢狀態?
  • 逾時是從使用者操作、請求發出還是頁面可見時開始計算?
  • 使用者取消、元件卸載、路由離開和逾時是否需要不同提示?
  • 目標瀏覽器是否支援靜態方法,降級是否要保留手動計時器?
  • 請求重試、去重、快取和結果競態如何處理?

30 秒回答框架

「我會為每個請求合併使用者取消訊號和 AbortSignal.timeout(),把 TimeoutErrorAbortError 與網路錯誤分開處理。逾時採用活動時間語義,所以頁面掛起或 bfcache 不應被誤報為線上耗時。新查詢先取消舊控制器,回應還要用請求序列或訊號檢查防止舊結果覆蓋新結果。對不支援靜態方法的瀏覽器保留控制器加計時器的降級,並測試取消、逾時、恢復和競態。」

分步驟深入解答

第一步:區分三種取消來源

AbortController.abort() 是應用主動取消;AbortSignal.timeout(ms) 在活動時間達到閾值後自動取消;AbortSignal.any([...]) 在任一輸入訊號取消時觸發。它們都能傳給 fetch,但業務應保留取消原因,避免把使用者離開頁面當作服務故障。

js
const controller = new AbortController();
const timeout = AbortSignal.timeout(5000);
const signal = AbortSignal.any([controller.signal, timeout]);

fetch(url, { signal });

第二步:按錯誤原因分類

逾時會以 TimeoutError DOMException 作為原因,使用者按下取消或瀏覽器停止操作通常是 AbortError。DNS、連線重置和 CORS 等問題可能表現為其他錯誤。捕獲邏輯應先檢查 signal.reason 或例外名稱,再決定提示、重試和日誌級別。

第三步:理解活動時間

timeout() 的計時基於活動時間,不是簡單的牆上時鐘。文件指出頁面處於 bfcache 或 Worker 被掛起時,計時會暫停。它適合「使用者可感知的活動請求窗口」,不等同於服務端絕對截止時間;服務端仍需自己的逾時和冪等策略。

第四步:處理搜尋競態

使用者連續輸入時,先取消舊控制器,再為新查詢建立訊號。即使舊請求已完成網路處理,回應回呼仍可能排隊;提交狀態前要檢查請求序列、目前關鍵字或 signal.aborted。取消請求不能取代結果版本校驗。

第五步:區分可取消讀寫

搜尋、圖片和建議讀取通常可以取消;支付、訂單或檔案上傳等寫入操作可能已在服務端生效。取消客戶端等待不等於撤銷服務端副作用。對寫入請求應使用冪等鍵、狀態查詢或後台任務,而不是簡單加一個更短的 timeout。

第六步:設計組合訊號的生命週期

元件卸載時呼叫控制器,路由切換時重用頁面級訊號,單一請求再疊加 timeout。不要把一個已取消的全域訊號永久共享給後續請求;每次操作都建立新的可用訊號。需要診斷時,為不同來源設定明確的 reason。

第七步:提供相容降級

舊瀏覽器若不支援 AbortSignal.timeoutany,可用 AbortControllersetTimeout 實現同等取消,但要清理計時器並統一錯誤原因。能力檢測應在執行時完成,預設路徑仍要正確處理沒有 AbortSignal 的環境。

第八步:驗證頁面生命週期和資源清理

測試快速輸入、元件卸載、路由離開、背景掛起、bfcache 返回、逾時、使用者取消、網路失敗和重複重試。確認網路請求被取消、計時器清理、舊結果不覆蓋新結果、錯誤提示準確,且沒有因捕獲例外而產生未處理 Promise 或狀態更新警告。

設計取捨與邊界

組合訊號減少了取消膠水程式碼,但不會讓服務端交易可撤銷,也不會保證回應絕不抵達。活動時間語義更貼近使用者體驗,卻不適合衡量端到端 SLA。逾時值應按請求類型、網路環境和重試預算設定,並與服務端截止時間協調。

不要用 Promise.race 單獨模擬逾時後就遺留底層請求;如果不傳 AbortSignal,網路和資源仍可能繼續消耗。也不要把所有中止都記錄為錯誤,否則使用者正常導覽會污染告警。

落地計畫與證據

先為搜尋讀取建立請求狀態機:建立訊號、發起請求、按原因分類、提交帶版本結果、卸載時取消。記錄請求類型、原因、活動耗時、重試次數和最終狀態,不記錄敏感查詢詞。

在支援和不支援靜態方法的瀏覽器中比較行為,覆蓋 bfcache、Worker、低網速和快速輸入。驗收要求無舊結果覆蓋、無計時器洩漏、取消提示準確、寫入請求不被誤取消,並保留服務端冪等或狀態查詢證據。

常見誤區與追問

把 TimeoutError 和 AbortError 混為一談

使用者取消、元件卸載和逾時的下一步不同。按 reason 分類,分別決定提示、日誌和重試。

以為客戶端取消撤銷了服務端寫入

網路請求可能已到達服務端。寫入操作需要冪等鍵、查詢狀態或補償流程。

用 Promise.race 取代真正取消

競速只改變等待結果,不會自動停止底層 fetch。把 AbortSignal 傳給請求並清理資源。

忽略 bfcache 的活動時間

頁面掛起期間 timeout 可能暫停。不要把恢復後的耗時直接當成線上請求 SLA。

如何避免舊搜尋結果覆蓋新結果?

取消舊控制器並不充分;提交前還要比較查詢序列、版本或目前關鍵字,確保回應順序不改變狀態。

公開來源

同類題目