JavaScript AbortController

概要: このチュートリアルでは、JavaScript の AbortController を使用して、進行中のネットワークリクエストをキャンセルする方法について説明します。

JavaScript AbortController の概要

AbortController は、ネットワークリクエストを中止できる Web API です。これは、AbortSignal をネットワークリクエストに関連付けることで機能します。このシグナルは、必要に応じてネットワークリクエストに中止を伝えるために使用できます。

以下は、AbortController を使用して fetch リクエストを中止する手順です。

まず、new キーワードを使用して AbortController オブジェクトを作成します。

const controller = new AbortController();Code language: JavaScript (javascript)

次に、AbortController オブジェクトの signal プロパティを取得します。signal プロパティは、AbortSignal オブジェクトのインスタンスです。

const signal = controller.signal;Code language: JavaScript (javascript)

この signal は、fetch リクエストに渡すことができ、その動作を制御できます。

3番目に、AbortSignal オブジェクトを fetch メソッドに渡します。

fetch(url, { signal });Code language: JavaScript (javascript)

あるいは、signalRequest オブジェクトに渡し、それを fetch メソッドで使用することもできます。

const request = new Request(url, { signal });
fetch(request);Code language: JavaScript (javascript)

4番目に、必要に応じて、AbortController オブジェクトの abort() メソッドを呼び出して fetch リクエストを中止します。

 controller.abort();Code language: JavaScript (javascript)

たとえば、2秒後に fetch をタイムアウトさせることができます。

setTimeout(() => controller.abort(), 2000);Code language: JavaScript (javascript)

fetch がすでに完了した後で abort() を呼び出しても問題ありません。fetch はそれを無視します。

5番目に、abort() メソッドを呼び出すと、abort シグナルが通知されます。signal オブジェクトで addEventListener を使用して abort イベントをリッスンできます。

signal.addEventListener('abort', () => {
    console.log(signal.aborted); // true
});Code language: JavaScript (javascript)

最後に、中止された fetch に対応できます。

fetch(url, { signal }).then(response => {
    return response.json();
}).then(data => {
    console.log(data);
}).catch(err => {
    if (err.name === 'AbortError') {
       console.log('Fetch aborted');
    }
});Code language: JavaScript (javascript)

AbortError は実際のエラーではないため、if ステートメントを使用して AbortError を特別に処理することに注意してください。

JavaScript AbortController の例

実際には、ユーザーインタラクションを扱うときに AbortController を使用します。たとえば、検索ページでは、ユーザーが新しい検索語を検索するときに、以前のリクエストを中止できます。

ここでは、Wikipedia で用語を検索するアプリを構築し、最後の検索が完了する前にユーザーが新しい検索を開始した場合、前の検索リクエストをキャンセルします。

ステップ 1. プロジェクトファイルを格納するための新しいディレクトリを作成します。

mkdir wikipedia-search-abortableCode language: JavaScript (javascript)

ステップ 2. プロジェクトディレクトリ内に次のディレクトリとファイルを作成します。

wikipedia-search-abortable
├── css
|  └── style.css
├── img
|  ├── spinner.svg
|  └── wikipedia-logo.png
├── index.html
└── js
   └── app.jsCode language: JavaScript (javascript)

ステップ 3. index.html ファイルに HTML 構造を作成します。

<!DOCTYPE html>
<html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Wikipedia Search</title>
        <script src="./js/app.js" defer></script>
        <link rel="stylesheet" href="./css/style.css">
    </head>

    <body>
        <header>
            <img src="./img/wikipedia-logo.png" alt="wikipedia">
            <h1>Wikipedia Search</h1>
            <form id="search">
                <input type="search" name="term" id="term" placeholder="Enter a search term...">
            </form>
        </header>

        <main>
            <div id="searchResult"></div>
            <div id="loading"></div>
            <div id="error"></div>
        </main>

    </body>

</html>Code language: HTML, XML (xml)

ステップ 4. 次のコードで app.js ファイルを修正します。

const form = document.querySelector('#search');
const termInput = document.querySelector('#term');
const error = document.querySelector('#error');
const loading = document.querySelector('#loading');
const resultsContainer = document.querySelector('#searchResult');

let controller = new AbortController();

const search = async (term) => {
  // Abort the previous request
  console.log('Abort the previous request');
  controller.abort();

  // Create a new controller for the new request
  controller = new AbortController();
  const signal = controller.signal;
  const url = `https://en.wikipedia.org/w/api.php?action=query&list=search&prop=info|extracts&inprop=url&utf8=&format=json&origin=*&srsearch=${term}`;

  const response = await fetch(url, { signal });
  const data = await response.json();
  return data.query.search;
};

const resetSearchResult = () => {
  error.innerHTML = '';
  loading.innerHTML = '';
  resultsContainer.innerHTML = '';
};

const handleSubmit = async (e) => {
  e.preventDefault();

  // reset search result
  resetSearchResult();

  // check if term is empty
  const term = termInput.value;
  if (term === '') return;

  try {
    // show the loading
    loading.innerHTML = `<img src='./img/spinner.svg' alt='loading...'>`;

    // make the search
    const results = await search(term);

    // show the search result
    resultsContainer.innerHTML = renderSearchResult(results);
  } catch (err) {
    // show the error
    error.innerHTML = `Something went wrong.`;
    console.error(err);
  } finally {
    // hide the loading
    loading.innerHTML = '';
  }
};

const renderSearchResult = (results) => {
  return results
    .map(({ title, snippet, pageid }) => {
      return `<article>
                <a href="https://en.wikipedia.org/?curid=${pageid}">
                    <h2>${title}</h2>
                </a>
                <div class="summary">${snippet}...</div>
            </article>`;
    })
    .join('');
};

form.addEventListener('submit', handleSubmit);Code language: JavaScript (javascript)

動作の仕組み。

まず、querySelector メソッドを使用して DOM 要素を選択します。

const form = document.querySelector('#search');
const termInput = document.querySelector('#term');
const error = document.querySelector('#error');
const loading = document.querySelector('#loading');
const resultsContainer = document.querySelector('#searchResult');Code language: JavaScript (javascript)

次に、fetch リクエストを中止するための AbortController を作成します。

let controller = new AbortController();Code language: JavaScript (javascript)

3番目に、入力検索語に基づいて Wikipedia API を呼び出す検索関数を定義します。

const search = async (term) => {
  // Abort the previous request
  console.log('Abort the previous request');
  controller.abort();

  // Create a new controller for the new request
  controller = new AbortController();
  const signal = controller.signal;
  const url = `https://en.wikipedia.org/w/api.php?action=query&list=search&prop=info|extracts&inprop=url&utf8=&format=json&origin=*&srsearch=${term}`;
  const response = await fetch(url, { signal });
  const data = await response.json();
  return data.query.search;
};Code language: JavaScript (javascript)

search() 関数では、ユーザーが新しい検索を開始した場合、前の検索リクエストをキャンセルするために、AbortController オブジェクトの abort() メソッドを呼び出します。

controller.abort();Code language: JavaScript (javascript)

新しいリクエストを行う前に、新しい AbortController オブジェクトを作成し、その AbortSignalfetch() メソッドに渡します。

controller = new AbortController();
const signal = controller.signal;
const url = `https://en.wikipedia.org/w/api.php?action=query&list=search&prop=info|extracts&inprop=url&utf8=&format=json&origin=*&srsearch=${term}`;
const response = await fetch(url, { signal });Code language: JavaScript (javascript)

ステップ 5. エラー、ローディング、および検索結果をリセットする関数を定義します。

const resetSearchResult = () => {
  error.innerHTML = '';
  loading.innerHTML = '';
  resultsContainer.innerHTML = '';
};Code language: JavaScript (javascript)

ステップ 6. フォームが送信されたときに検索を実行する handleSubmit() 関数を定義します。

const handleSubmit = async (e) => {
  e.preventDefault();

  // reset search result
  resetSearchResult();

  // check if term is empty
  const term = termInput.value;
  if (term === '') return;

  try {
    // show the loading
    loading.innerHTML = `<img src='./img/spinner.svg' alt='loading...'>`;

    // make the search
    const results = await search(term);

    // show the search result
    resultsContainer.innerHTML = renderSearchResult(results);
  } catch (err) {
    // show the error
    error.innerHTML = `Something went wrong.`;
    console.error(err);
  } finally {
    // hide the loading
    loading.innerHTML = '';
  }
};Code language: JavaScript (javascript)

動作の仕組み。

まず、フォームがサーバーに送信されないようにします。

e.preventDefault();Code language: JavaScript (javascript)

次に、検索結果をリセットするために resetSearchResult 関数を呼び出します。

resetSearchResult();Code language: JavaScript (javascript)

3番目に、検索語が空白の場合はすぐに戻ります。

const term = termInput.value;
if (term === '') return;Code language: JavaScript (javascript)

4番目に、検索リクエストを行う前にローディングプログレスインジケーターを表示します。

loading.innerHTML = `<img src='./img/spinner.svg' alt='loading...'>`;Code language: JavaScript (javascript)

5番目に、検索リクエストを行います。

const results = await search(term);Code language: JavaScript (javascript)

6番目に、検索結果を表示します。

resultsContainer.innerHTML = renderSearchResult(results);Code language: JavaScript (javascript)

7番目に、ユーザーフレンドリーなユーザーメッセージを表示し、コンソールにエラーの詳細を記録します。

 } catch (err) {
    // show the error
    error.innerHTML = `Something went wrong.`;
    console.error(err);
 }Code language: JavaScript (javascript)

8番目に、エラーが発生したかどうかに関係なく、ローディングインジケーターを非表示にします。

} finally {
  // hide the loading
  loading.innerHTML = '';
}Code language: JavaScript (javascript)

9番目に、検索結果をレンダリングする関数 renderSearchResult を定義します。

const renderSearchResult = (results) => {
  return results
    .map(({ title, snippet, pageid }) => {
      return `<article>
                <a href="https://en.wikipedia.org/?curid=${pageid}">
                    <h2>${title}</h2>
                </a>
                <div class="summary">${snippet}...</div>
            </article>`;
    })
    .join('');
};Code language: JavaScript (javascript)

最後に、フォームの送信イベントハンドラーとして handleSubmit 関数を登録します。

form.addEventListener('submit', handleSubmit);Code language: JavaScript (javascript)

プロジェクトのソースコードをダウンロードする

プロジェクトのソースコードをダウンロードする

まとめ

  • 不要なネットワークリクエストを防ぎ、ユーザーインタラクションを効率的に処理するために、Web リクエストを中止するには AbortController を使用します。
このチュートリアルは役に立ちましたか?