日本語

JavaScriptのAbortControllerを使用して、fetchリクエスト、タイマーなどの非同期操作を効果的にキャンセルする方法を学び、よりクリーンで高性能なコードを確保します。

JavaScript AbortController: 非同期操作のキャンセルをマスターする

現代のWeb開発では、非同期操作は至る所に存在します。APIからのデータの取得、タイマーの設定、ユーザーインタラクションの処理は、独立して実行され、潜在的に長期間にわたって実行されるコードを伴うことがよくあります。ただし、これらの操作が完了する前にキャンセルする必要があるシナリオがあります。ここで、JavaScriptのAbortControllerインターフェースが登場します。これは、DOM操作やその他の非同期タスクにキャンセルリクエストを通知するためのクリーンで効率的な方法を提供します。

キャンセルが必要な理由を理解する

技術的な詳細に入る前に、非同期操作をキャンセルすることがなぜ重要なのかを理解しましょう。次の一般的なシナリオを考えてみてください。

AbortControllerとAbortSignalの紹介

AbortControllerインターフェースは、非同期操作のキャンセルという問題を解決するために設計されています。これは、次の2つの主要なコンポーネントで構成されています。

基本的な使用法: Fetchリクエストのキャンセル

fetchリクエストをキャンセルする簡単な例から始めましょう。


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

fetch('https://api.example.com/data', { signal })
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => {
    console.log('Data:', data);
  })
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Fetch aborted');
    } else {
      console.error('Fetch error:', error);
    }
  });

// To cancel the fetch request:
controller.abort();

説明:

  1. AbortControllerインスタンスを作成します。
  2. controllerから関連付けられたAbortSignalを取得します。
  3. signalfetchオプションに渡します。
  4. リクエストをキャンセルする必要がある場合は、controller.abort()を呼び出します。
  5. .catch()ブロックで、エラーがAbortErrorであるかどうかを確認します。そうである場合、リクエストがキャンセルされたことがわかります。

AbortErrorの処理

controller.abort()が呼び出されると、fetchリクエストはAbortErrorで拒否されます。このエラーをコードで適切に処理することが重要です。そうしないと、未処理のPromiseリジェクションや予期しない動作につながる可能性があります。

エラー処理を含む、より堅牢な例を次に示します。


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

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data', { signal });
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    const data = await response.json();
    console.log('Data:', data);
    return data;
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Fetch aborted');
      return null; // Or throw the error to be handled further up
    } else {
      console.error('Fetch error:', error);
      throw error; // Re-throw the error to be handled further up
    }
  }
}

fetchData();

// To cancel the fetch request:
controller.abort();

AbortErrorを処理するためのベストプラクティス:

AbortSignalを使用したタイマーのキャンセル

AbortSignalは、setTimeoutまたはsetIntervalで作成されたタイマーをキャンセルするためにも使用できます。組み込みのタイマー関数がAbortSignalを直接サポートしていないため、これにはもう少し手作業が必要です。アボートシグナルをリッスンし、トリガーされたときにタイマーをクリアするカスタム関数を作成する必要があります。


function cancellableTimeout(callback, delay, signal) {
  let timeoutId;

  const timeoutPromise = new Promise((resolve, reject) => {
    timeoutId = setTimeout(() => {
      resolve(callback());
    }, delay);

    signal.addEventListener('abort', () => {
      clearTimeout(timeoutId);
      reject(new Error('Timeout Aborted'));
    });
  });

  return timeoutPromise;
}

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


cancellableTimeout(() => {
  console.log('Timeout executed');
}, 2000, signal)
.then(() => console.log("Timeout finished successfully"))
.catch(err => console.log(err));

// To cancel the timeout:
controller.abort();

説明:

  1. cancellableTimeout関数は、コールバック、遅延、およびAbortSignalを引数として取ります。
  2. setTimeoutを設定し、タイムアウトIDを保存します。
  3. abortイベントをリッスンするAbortSignalにイベントリスナーを追加します。
  4. abortイベントがトリガーされると、イベントリスナーはタイムアウトをクリアし、プロミスを拒否します。

イベントリスナーのキャンセル

タイマーと同様に、AbortSignalを使用してイベントリスナーをキャンセルできます。これは、アンマウントされているコンポーネントに関連付けられているイベントリスナーを削除する場合に特に役立ちます。


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

const button = document.getElementById('myButton');

button.addEventListener('click', () => {
  console.log('Button clicked!');
}, { signal });

// To cancel the event listener:
controller.abort();

説明:

  1. signaladdEventListenerメソッドのオプションとして渡します。
  2. controller.abort()が呼び出されると、イベントリスナーは自動的に削除されます。

ReactコンポーネントでのAbortController

Reactでは、コンポーネントがアンマウントされたときにAbortControllerを使用して非同期操作をキャンセルできます。これは、アンマウントされたコンポーネントの更新によって発生するメモリリークやエラーを防ぐために不可欠です。useEffectフックを使用した例を次に示します。


import React, { useState, useEffect } from 'react';

function MyComponent() {
  const [data, setData] = useState(null);

  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;

    async function fetchData() {
      try {
        const response = await fetch('https://api.example.com/data', { signal });
        if (!response.ok) {
          throw new Error(`HTTP error! Status: ${response.status}`);
        }
        const data = await response.json();
        setData(data);
      } catch (error) {
        if (error.name === 'AbortError') {
          console.log('Fetch aborted');
        } else {
          console.error('Fetch error:', error);
        }
      }
    }

    fetchData();

    return () => {
      controller.abort(); // Cancel the fetch request when the component unmounts
    };
  }, []); // Empty dependency array ensures this effect runs only once on mount

  return (
    
{data ? (

Data: {JSON.stringify(data)}

) : (

Loading...

)}
); } export default MyComponent;

説明:

  1. useEffectフック内にAbortControllerを作成します。
  2. signalfetchリクエストに渡します。
  3. useEffectフックからクリーンアップ関数を返します。この関数は、コンポーネントがアンマウントされるときに呼び出されます。
  4. クリーンアップ関数内で、controller.abort()を呼び出してfetchリクエストをキャンセルします。

高度なユースケース

AbortSignalのチェーン

場合によっては、複数のAbortSignalをチェーンしたい場合があります。たとえば、子コンポーネントの操作をキャンセルする必要がある親コンポーネントがあるかもしれません。これは、新しいAbortControllerを作成し、そのシグナルを親コンポーネントと子コンポーネントの両方に渡すことで実現できます。

サードパーティライブラリでのAbortControllerの使用

AbortSignalを直接サポートしていないサードパーティライブラリを使用している場合は、ライブラリのキャンセルメカニズムと連携するようにコードを適合させる必要がある場合があります。これには、AbortSignalを処理する独自の関数でライブラリの非同期関数をラップすることが含まれる場合があります。

AbortControllerを使用する利点

ブラウザの互換性

AbortControllerは、Chrome、Firefox、Safari、Edgeなどの最新のブラウザで広くサポートされています。最新の情報については、MDN Web Docsの互換性テーブルを確認してください。

ポリフィル

AbortControllerをネイティブにサポートしていない古いブラウザの場合は、ポリフィルを使用できます。ポリフィルとは、古いブラウザで新しい機能の機能を提供するコードのことです。オンラインで利用できるAbortControllerポリフィルがいくつかあります。

結論

AbortControllerインターフェースは、JavaScriptで非同期操作を管理するための強力なツールです。AbortControllerを使用すると、キャンセルを適切に処理する、よりクリーンで、より高性能で、より堅牢なコードを作成できます。APIからのデータの取得、タイマーの設定、イベントリスナーの管理など、AbortControllerはWebアプリケーション全体の品質を向上させるのに役立ちます。

さらに読む