使用 Fetch API 時實作錯誤處理

Umar Hansa
Umar Hansa

本文說明使用 Fetch API 時的一些錯誤處理方法。Fetch API 可讓您對遠端網路資源提出要求。當您發出遠端網路呼叫時,網頁就可能會發生各種網路錯誤。

以下各節將說明潛在錯誤,並說明如何編寫程式碼,提供可抵禦錯誤和意外網路情況的合理功能。富有彈性的程式碼可確保使用者滿意,持續為網站提供服務。

預測可能發生的網路錯誤

本節說明的情況是,使用者建立名為 "My Travels.mp4" 的新影片,然後嘗試將影片上傳至影片分享網站。

使用 Fetch 時,您可以輕鬆考量使用者成功上傳影片的順利路徑。不過,還有其他路徑不太順暢,但網頁程式開發人員必須規劃這些路徑。這種不理想的路徑可能會因使用者操作錯誤、環境狀況不佳或影片分享網站發生錯誤而發生。

使用者錯誤示例

  • 使用者上傳的是圖片檔 (例如 JPEG),而不是影片檔案。
  • 使用者開始上傳錯誤的影片檔案。然後,在上傳過程中,使用者指定要上傳的正確影片檔案。
  • 使用者在影片上傳期間不小心按了 [取消上傳]。

環境變更的例子

  • 影片上傳期間,網際網路連線中斷。
  • 瀏覽器在影片上傳期間重新啟動。
  • 影片分享網站的伺服器在上傳影片時重新啟動。

影片分享網站錯誤示例

  • 影片分享網站無法處理包含空格的檔案名稱。系統會預期名稱為 "My_Travels.mp4""MyTravels.mp4" 的值,而不是 "My Travels.mp4"
  • 影片分享網站無法上傳超過可接受檔案大小上限的影片。
  • 影片分享網站不支援上傳影片中的視訊編碼。

這些例子在現實世界中都可能發生,也確實發生過。您可能曾經遇到過類似的情況!請從上述每個類別中挑選一個範例,然後討論下列要點:

  • 如果影片分享服務無法處理指定範例,預設行為為何?
  • 使用者在這個例子中預期會發生什麼事?
  • 我們可如何改善這項程序?
動作 使用者開始上傳錯誤的影片檔案。然後,在上傳過程中,使用者指定要上傳的正確影片檔案。
預設情況 原始檔案會繼續在背景上傳,同時上傳新檔案。
使用者預期的結果 使用者預期原始上傳作業會停止,以免浪費額外的網路頻寬。
可改善之處 JavaScript 會在新檔案開始上傳前,取消原始檔案的擷取要求。
動作 使用者在上傳影片時,網路連線中斷。
預設情況 上傳進度列似乎停滯在 50%。最後,Fetch API 會逾時,上傳的資料就會遭到捨棄。當網路連線恢復後,使用者必須重新上傳檔案。
使用者期望 使用者希望在無法上傳檔案時收到通知,並且希望在連線後,上傳作業會自動恢復至 50%。
可改善之處 上傳頁面會向使用者說明網際網路連線問題,並向使用者保證,上傳會在網際網路連線恢復後繼續進行。
動作 影片分享網站無法處理含有空格的檔案名稱。系統預期的名稱為「My_Travels.mp4」或「MyTravels.mp4」,而不是「My Travels.mp4」。
預設情況 使用者必須等待上傳作業完成。檔案上傳完成後,進度列會顯示「100%」,並顯示「Please try again」訊息。
使用者預期的結果 使用者希望在開始上傳前,或至少在上傳過程的頭幾秒內,就會收到檔案名稱限制的通知。
有哪些可以改善 理想情況下,影片分享服務應支援含有空格的檔案名稱。您也可以在開始上傳前,先通知使用者檔案名稱的限制。或者,影片分享服務應拒絕上傳,並顯示詳細的錯誤訊息。

使用 Fetch API 處理錯誤

請注意,下列程式碼範例使用的是頂層 await (瀏覽器支援),因為這項功能可簡化程式碼。

Fetch API 擲回錯誤時

這個範例使用 try/catch 區塊陳述式,擷取 try 區塊中擲回的任何錯誤。舉例來說,如果 Fetch API 無法擷取指定的資源,系統就會擲回錯誤。在這種 catch 區塊中,請務必提供有意義的使用者體驗。如果您向使用者顯示代表某種進度的常見使用者介面 (即旋轉圖示),可以在 catch 區塊中採取下列行動:

  1. 從頁面中移除旋轉圖示。
  2. 提供實用的訊息,說明問題原因以及使用者可採取的選項。
  3. 根據可用的選項,向使用者顯示「再試一次」按鈕。
  4. 在幕後將錯誤詳細資料傳送至錯誤追蹤服務或後端。這項動作會記錄錯誤,以便在後續階段進行診斷。
try {
  const response = await fetch('https://website');
} catch (error) {
  // TypeError: Failed to fetch
  console.log('There was an error', error);
}

稍後診斷您記錄的錯誤時,您可以編寫測試案例,在使用者發現問題之前就先擷取這類錯誤。視錯誤而定,測試可能是單元、整合測試或驗收測試。

網路狀態碼代表錯誤時

這個程式碼範例會向 HTTP 測試服務提出要求,該服務一律會傳回 HTTP 狀態碼 429 Too Many Requests。有趣的是,回應未達到 catch 區塊。根據其他某些狀態碼,404 狀態不會傳回網路錯誤,而會照常解析。

如要確認 HTTP 狀態碼是否成功,您可以使用下列任一選項:

  • 使用 Response.ok 屬性判斷狀態碼是否介於 200299 之間。
  • 使用 Response.status 屬性判斷回應是否成功。
  • 使用任何其他中繼資料 (例如 Response.headers) 來評估回應是否成功。
let response;

try {
  response = await fetch('https://httpbin.org/status/429');
} catch (error) {
  console.log('There was an error', error);
}

// Uses the 'optional chaining' operator
if (response?.ok) {
  console.log('Use the response here!');
} else {
  console.log(`HTTP Response Code: ${response?.status}`)
}

最佳做法是與貴機構和團隊中的人員合作,瞭解可能的 HTTP 回應狀態碼。後端開發人員、開發人員作業人員和服務工程師有時可以提供您可能未預料到的邊緣案例相關獨特洞察資料。

剖析網路回應時發生錯誤

這個程式碼範例說明解析回應主體時可能發生的另一種錯誤。Response 介面提供方便的方法,可剖析不同類型的資料,例如文字或 JSON。在下列程式碼中,系統會向 HTTP 測試服務發出網路要求,而此服務會傳回 HTML 字串做為回應主體。不過,嘗試將回應主體剖析為 JSON,系統會擲回錯誤。

let json;

try {
  const response = await fetch('https://httpbin.org/html');
  json = await response.json();
} catch (error) {
  if (error instanceof SyntaxError) {
    // Unexpected token < in JSON
    console.log('There was a SyntaxError', error);
  } else {
    console.log('There was an error', error);
  }
}

if (json) {
  console.log('Use the JSON here!', json);
}

您必須準備好程式碼,以便接收各種回應格式,並確認意外回應不會導致網頁無法正常顯示。

請考慮下列情況:您有一個傳回有效 JSON 回應的遠端資源,且該回應已透過 Response.json() 方法成功剖析。服務可能會中斷。關閉後,系統會傳回 500 Internal Server Error。如果在剖析 JSON 時未使用適當的錯誤處理技巧,系統可能會擲回未處理的錯誤,導致網頁無法正常運作。

網路要求必須在完成前取消

這個程式碼範例使用 AbortController 取消執行中的要求。進行中的網路要求是指已啟動但尚未完成的網路要求。

您可能需要取消進行中的請求的情況不盡相同,但最終仍取決於您的用途和環境。以下程式碼示範如何將 AbortSignal 傳遞至 Fetch API。AbortSignal 會附加至 AbortController,而 AbortController 包含 abort() 方法,可向瀏覽器表示應取消網路要求。

const controller = new AbortController();
const signal = controller.signal;

// Cancel the fetch request in 500ms
setTimeout(() => controller.abort(), 500);

try {
  const url = 'https://httpbin.org/delay/1';
  const response = await fetch(url, { signal });
  console.log(response);
} catch (error) {
  // DOMException: The user aborted a request.
  console.log('Error: ', error)
}

結論

處理錯誤時,定義可能出錯的各個部分是一項重要環節。針對每個情境,確認您已為使用者準備好備用選項。請針對擷取要求,問自己以下問題:

  • 如果目標伺服器發生故障,會發生什麼情況?
  • 如果 Fetch 收到非預期的回應,會發生什麼事?
  • 如果使用者的網路連線失敗,會發生什麼情況?

視網頁複雜度而定,您也可以勾勒出流程圖,說明不同情境下的功能和使用者介面。