你真的會用 Fetch 嗎?深入 AbortController 實現請求取消

4 分鐘

1. Fetch API:現代 Web 請求的基石

Fetch API 已經取代了老舊的 XMLHttpRequest,成為瀏覽器中發起 HTTP 請求的標準方式。它基於 Promise,提供了更簡潔、更強大的請求能力。

然而,Fetch API 預設並沒有提供一個直接取消請求的機制。在許多情境下,請求取消是一個非常重要的功能:

  • 效能最佳化: 使用者快速切換頁面或輸入搜尋詞時,取消舊的、不再需要的請求可以節省頻寬和伺服器資源。
  • 避免競態條件: 在「搜尋即時輸入」的情境中,如果舊的請求比新的請求回傳得慢,可能會導致 UI 顯示過時的資料。
  • 防止記憶體洩漏: 在元件生命週期中發起的請求,如果元件在請求完成前被卸載,嘗試更新一個不存在的元件可能會導致錯誤或記憶體洩漏。

2. AbortController:通用的取消訊號

AbortController 是一個通用的 Web API,它提供了一種機制來中止一個或多個 Web 請求。它獨立於 Fetch API,但可以與 Fetch 完美配合。

AbortController 的核心思想是:

  1. 建立一個 AbortController 實例。
  2. 從這個實例中取得一個 AbortSignal 物件。
  3. AbortSignal 傳遞給可中止的 Web API(如 fetch)。
  4. 當你需要取消時,呼叫 AbortController 實例的 abort() 方法。

3. AbortControllerFetch 的結合

讓我們透過一個範例來看看如何使用 AbortController 取消 Fetch 請求。

const controller = new AbortController();
const signal = controller.signal; // 获取信号对象

async function fetchDataWithCancellation(url) {
  try {
    console.log('Fetching data...');
    const response = await fetch(url, { signal }); // 将信号传递给 fetch
    const data = await response.json();
    console.log('Data received:', data);
    return data;
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Fetch request was aborted.');
    } else {
      console.error('Fetch error:', error);
    }
  }
}

// 示例用法
const promise = fetchDataWithCancellation('https://jsonplaceholder.typicode.com/posts/1');

// 假设在 500 毫秒后,我们决定取消这个请求
setTimeout(() => {
  controller.abort(); // 调用 abort() 方法取消请求
  console.log('Request aborted by timeout.');
}, 500);

controller.abort() 被呼叫時,fetch 請求會立即中止,並拋出一個 AbortError。你需要在 catch 區塊中捕捉這個錯誤,並檢查 error.name === 'AbortError' 來區分是取消操作還是其他網路錯誤。

4. 實際應用情境

a. 搜尋即時輸入(Search-as-you-type)

當使用者在搜尋框中快速輸入時,每次輸入都可能觸發一個新的請求。我們可以取消前一個未完成的請求,只保留最新的請求。

let currentController = null;

document.getElementById('searchInput').addEventListener('input', (event) => {
  if (currentController) {
    currentController.abort(); // 取消上一个请求
  }
  currentController = new AbortController();
  const signal = currentController.signal;

  const query = event.target.value;
  if (query.length > 2) {
    fetchDataWithCancellation(`/api/search?q=${query}`, signal);
  }
});

b. 元件卸載時取消請求(Component Unmount)

在 React 或 Vue 等框架中,當元件卸載時,取消其內部發起的未完成請求可以有效避免記憶體洩漏和不必要的 UI 更新。

// React 示例
useEffect(() => {
  const controller = new AbortController();
  fetchDataWithCancellation('/api/data', controller.signal);

  return () => {
    controller.abort(); // 组件卸载时取消请求
  };
}, []);

結論

AbortController 是現代 Web 開發中一個不可或缺的工具。它為 Fetch API 和其他非同步操作提供了原生的取消機制,幫助開發者建構更健壯、更高效、使用者體驗更好的應用程式。掌握 AbortController 的使用,是成為一名優秀前端開發者的必備技能之一。