AbortControllerを知らないなんて

[![表紙](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/4285973f-73ad-c687-6073-0c2ce183f318.png)](https://leapcell.io/?lc_t=q_abortctrl)

多くの開発者が`AbortController`を理解していると思っているかもしれませんが、その機能は基本的な用途をはるかに超えています。たとえば、`fetch`リクエストのキャンセル、イベントリスナーの管理、Reactフックとの連携などです。

本当に`AbortController`の威力を知っていますか？それでは見てみましょう。

## AbortControllerを使った`fetch`リクエストのキャンセル

`AbortController`を`fetch`と一緒に使うことは、最も一般的な用途です。

以下は、`AbortController`を使ってキャンセル可能な`fetch`リクエストを作成する例です：

```javascript
fetchButton.onclick = async () => {
  const controller = new AbortController();
  // キャンセルボタンを追加
  abortButton.onclick = () => controller.abort();
  try {
    const response = await fetch('/json', { signal: controller.signal });
    const data = await response.json();
    // ビジネスロジックをここで処理
  } catch (error) {
    const isUserAbort = error.name === 'AbortError';
    // AbortControllerでリクエストをキャンセルするとAbortErrorがスローされる
  }
};
```

上記の例は、`AbortController`が導入される前には不可能だった、ネットワークリクエストのプログラムによるキャンセルを実現しています。キャンセルされると、ブラウザは`fetch`を停止し、ネットワーク帯域幅を節約します。重要なのは、このキャンセルが必ずしもユーザーによって開始される必要はないという点です。

`controller.signal`は`AbortSignal`オブジェクトを提供し、`fetch`のような非同期操作との通信を可能にし、それらをキャンセルできるようにします。

複数のシグナルを1つのシグナルに結合する場合は、`AbortSignal.any()`を使用できます。以下のように：

```javascript
try {
  const controller = new AbortController();
  const timeoutSignal = AbortSignal.timeout(5000);
  const response = await fetch(url, {
    // いずれかのシグナルがトリガーされた場合にfetchを中止
    signal: AbortSignal.any([controller.signal, timeoutSignal]),
  });
  const data = await response.json();
} catch (error) {
  if (error.name === 'AbortError') {
    // ユーザーにキャンセルを通知
  } else if (error.name === 'TimeoutError') {
    // タイムアウトを通知
  } else {
    // その他のエラー（ネットワーク問題など）を処理
    console.error(`種類: ${error.name}, メッセージ: ${error.message}`);
  }
}
```

### `AbortController`と`AbortSignal`の違い

- **AbortController**: `controller.abort()`を通じて関連するシグナルを明示的にキャンセルするために使用します。
- **AbortSignal**: シグナルオブジェクトを表します。それ自体では何もキャンセルできませんが、キャンセル状態を通信します。

`AbortSignal`では以下が可能です：

- `signal.aborted`でキャンセルされているかを確認
- `abort`イベントをリッスン：

```javascript
if (signal.aborted) {
}
signal.addEventListener('abort', () => {});
```

リクエストが`AbortController`でキャンセルされると、サーバーはそれを処理せず応答も送信しないため、帯域幅を節約し、クライアント側のパフォーマンスを向上させます。

## AbortControllerの主なユースケース

### WebSocket接続のキャンセル

従来のWebSocketのような古いAPIは、`AbortSignal`をネイティブにサポートしていません。その代わり、次のようにキャンセルを実装できます：

```javascript
function abortableSocket(url, signal) {
  const socket = new WebSocket(url);
  if (signal.aborted) {
    socket.close();
    // すでにキャンセルされている場合は即時終了
  }
  signal.addEventListener('abort', () => socket.close());
  return socket;
}
```

注意：`AbortSignal`がすでにキャンセルされている場合、`abort`イベントはトリガーされません。そのため、このケースを事前に確認して処理する必要があります。

### イベントリスナーの削除

従来の方法では、イベントリスナーを削除するには完全に同じ関数参照を渡す必要がありました：

```javascript
window.addEventListener('resize', () => doSomething());
window.removeEventListener('resize', () => doSomething()); // これは機能しない
```

`AbortController`を使用すると、これが簡単になります：

```javascript
const controller = new AbortController();
const { signal } = controller;
window.addEventListener('resize', () => doSomething(), { signal });
// abort()を呼び出すことでイベントリスナーを削除
controller.abort();
```

古いブラウザでは、`AbortController`をサポートするためにポリフィルを追加することを検討してください。

### Reactフック内での非同期タスクの管理

Reactでは、コンポーネントが更新される前に以前の非同期タスクが完了しない場合、エフェクトが並行して実行されることがあります：

```javascript
function FooComponent({ something }) {
  useEffect(async () => {
    const data = await fetch(url + something);
    // データを処理
  }, [something]);
  return <>...</>;
}
```

このような問題を避けるには、`AbortController`を使用して以前のタスクをキャンセルします：

```javascript
function FooComponent({ something }) {
  useEffect(() => {
    const controller = new AbortController();
    const { signal } = controller;
    (async () => {
      const data = await fetch(url + something, { signal });
      // レスポンスを処理
    })();
    return () => controller.abort();
  }, [something]);
  return <>...</>;
}
```

### Node.jsでのAbortControllerの使用

最新のNode.jsでは、`AbortController`に対応した`setTimeout`実装が含まれています：

```javascript
const { setTimeout: setTimeoutPromise } = require('node:timers/promises');
const controller = new AbortController();
const { signal } = controller;

setTimeoutPromise(1000, 'foobar', { signal })
  .then(console.log)
  .catch((error) => {
    if (error.name === 'AbortError') console.log('タイムアウトが中止されました');
  });

controller.abort();
```

ブラウザの`setTimeout`とは異なり、この実装はコールバックを受け入れません。その代わりに`.then()`や`await`を使用します。

### 高度なスケジューリングのためのTaskController

ブラウザはタスクの優先順位付けのために`scheduler.postTask()`を採用し、`TaskController`が`AbortController`を拡張します。これを使ってタスクをキャンセルしたり、優先順位を動的に調整できます：

```javascript
const taskController = new TaskController();
scheduler
  .postTask(() => console.log('タスクを実行'), { signal: taskController.signal })
  .then((result) => console.log(result))
  .catch((error) => console.error('エラー:', error));

taskController.abort();
```

優先順位制御が不要な場合は、単に`AbortController`を使用できます。

## 結論

`AbortController`は、現代のJavaScript開発において不可欠なツールであり、非同期タスクを管理およびキャンセルするための標準化された方法を提供します。

そのブラウザおよびNode.js環境への統合は、その汎用性と重要性を示しています。

`AbortController`を知らなかった方も、今こそその能力を最大限に活用し、非同期プログラミングツールキットの基盤として採用する時です。

---

### [私たちはLeapcell、Node.jsプロジェクトのクラウドデプロイの最適解です。](https://leapcell.io/?lc_t=q_abortctrl)!

[![Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/a5c80ca7-1dee-d8eb-b300-424f65194560.png)](https://leapcell.io/?lc_t=q_abortctrl)

[Leapcell](https://leapcell.io/?lc_t=q_abortctrl)は、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです：

**複数言語サポート**

- Node.js、Python、Go、Rustで開発できます。

**無制限のプロジェクトデプロイ**

- 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。

**比類のないコスト効率**

- 使用量に応じた支払い、アイドル時間は課金されません。
- 例: $25で6.94Mリクエスト、平均応答時間60ms。

**洗練された開発者体験**

- 直感的なUIで簡単に設定できます。
- 完全自動化されたCI/CDパイプラインとGitOps統合。
- 実行可能なインサイトのためのリアルタイムのメトリクスとログ。

**簡単なスケーラビリティと高パフォーマンス**

- 高い同時実行性を容易に処理するためのオートスケーリング。
- ゼロ運用オーバーヘッド — 構築に集中できます。

[ドキュメントで詳細を確認！](https://docs.leapcell.io/)

[![Try Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/e23123df-63a2-6c4e-ece8-6a5629590731.png)](https://leapcell.io/?lc_t=q_abortctrl)

Xでフォローする：[@LeapcellHQ](https://x.com/LeapcellHQ)

---

[ブログでこの記事を読む](https://leapcell.io/blog/you-dont-know-abortcontroller)