Fetch を本当に使いこなせていますか?AbortController によるリクエストのキャンセル

5 分

1. Fetch API:現代の Web リクエストの基盤

Fetch API は古い XMLHttpRequest に代わり、ブラウザで HTTP リクエストを行う標準的な手段になりました。Promise を基盤とし、より簡潔で強力なリクエスト機能を提供します。

しかし、Fetch API には標準でリクエストを直接キャンセルする仕組みがありません。多くの場面で、リクエストのキャンセルは非常に重要です。

  • パフォーマンスの最適化: ユーザーがページを素早く切り替えたり検索語を入力したりするとき、古くなって不要になったリクエストをキャンセルすれば、帯域幅とサーバー資源を節約できます。
  • 競合状態の回避: 「入力しながら検索」する場面では、古いリクエストが新しいリクエストより遅く返ると、UI に古いデータが表示される可能性があります。
  • メモリリークの防止: コンポーネントのライフサイクル内で開始したリクエストが完了する前にコンポーネントがアンマウントされると、存在しないコンポーネントを更新しようとしてエラーやメモリリークが起きる可能性があります。

2. AbortController:汎用的なキャンセルシグナル

AbortController は、1 つまたは複数の Web リクエストを中止する仕組みを提供する汎用的な Web API です。Fetch API とは独立していますが、Fetch と完全に連携できます。

AbortController の基本的な考え方は次のとおりです。

  1. AbortController のインスタンスを作成します。
  2. そのインスタンスから AbortSignal オブジェクトを取得します。
  3. AbortSignal を、fetch など中止可能な Web API に渡します。
  4. キャンセルが必要になったら、AbortController インスタンスの abort() メソッドを呼び出します。

3. AbortControllerFetch の組み合わせ

例を通して、AbortControllerFetch リクエストをキャンセルする方法を見てみましょう。

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. コンポーネントのアンマウント時にリクエストをキャンセル

React や Vue などのフレームワークでは、コンポーネントがアンマウントされるときに、その内部で開始した未完了のリクエストをキャンセルすることで、メモリリークや不要な UI 更新を効果的に防げます。

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

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

まとめ

AbortController は現代の Web 開発に欠かせないツールです。Fetch API やその他の非同期処理にネイティブなキャンセル機構を提供し、より堅牢で効率的、かつ優れたユーザー体験を持つアプリケーションの構築に役立ちます。AbortController の使い方を身につけることは、優れたフロントエンド開発者になるために必要なスキルの一つです。