フロントエンド・API連携

AbortControllerが非同期リクエストを中断するブラウザの内部挙動

検索フォームに文字を入力するたび、候補取得のリクエストを送る。ユーザーが入力を続ければ、a、ab、abcと通信が積み上がる。ここで古いリクエストの応答が後から返ると、画面には最新の検索結果ではなく、ひとつ前の結果が表示されることがある。…

AbortControllerが非同期リクエストを中断するブラウザの内部挙動

この問題は、通信速度だけでは解決しない。必要なのは、ユーザーの操作に合わせて不要になった非同期処理を止める制御だ。

JavaScriptのAbortControllerは、そのための標準的な仕組みである。fetchに中断用のAbortSignalを渡し、不要になったタイミングでcontroller.abort()を呼び出す。すると、ブラウザ側で対象の非同期処理へ中断の通知が伝わり、fetchPromiseAbortErrorとして拒否される。

ただし、ここで誤解しやすい境界がある。AbortControllerが止めるのは、ブラウザ側から見たリクエスト処理だ。すでにサーバーへ到達して実行中のデータベース処理や、バックエンドのトランザクションまで自動的に取り消すわけではない。

この境界を理解しないまま導入すると、見た目の離脱率は下がっても、サーバー側では不要な処理が残る。フロントエンドの改善は、通信を止めることだけでは終わらない。ユーザーの痛み、画面の状態、APIの設計、バックエンドの処理時間まで含めて検証する必要がある。

AbortControllerの仕組みは「処理」ではなく「中断通知」の伝達

AbortControllerは、非同期処理そのものを実行するオブジェクトではない。役割は、中断の意思を伝えることにある。

コントローラーを生成すると、そこにはsignalというAbortSignalが紐づく。fetchなどの非同期処理にこのシグナルを渡しておき、必要なタイミングでabort()を呼び出す。するとシグナルの状態が変化し、処理を監視している側へ中断が通知される。

関係を整理すると、次の構造になる。

  • AbortController:中断を指示する側
  • AbortSignal:中断状態を伝える側
  • fetch:シグナルを監視し、通知を受けて処理を拒否する側
  • AbortError:中断によってPromiseが失敗したことを示すエラー

controller.abort()を呼び出すと、紐づくcontroller.signalではabortイベントが発生し、signal.abortedtrueになる。一度abortedになったシグナルは、元の状態へ戻らない。この一方向性が、設計上かなり重要だ。

同じコントローラーを再利用して新しい通信を始めようとしても、シグナルはすでに中断済みである。新しいリクエストには、基本的に新しいAbortControllerを用意する必要がある。

たとえば検索処理なら、入力イベントのたびにコントローラーを作り直す設計になる。前回のコントローラーを保持しておき、次の検索が始まる直前にabort()を呼ぶ。これで「前の検索はもう必要ない」という状態を、ブラウザの通信処理へ渡せる。

ここで押さえたいのは、シグナルが処理の本体ではなく、状態を共有するための窓口だという点だ。AbortSignalは、複数の非同期処理に渡せる。ひとつの中断操作を複数の処理へ連動させられる理由も、この構造にある。

AbortControllerは、非同期処理を魔法のように消す道具ではない。不要になった処理へ、終了してよいという意思を伝えるための標準インターフェースだ。

fetch APIでリクエストが中断されるまで

fetchで中断を扱う場合、RequestInitsignalプロパティへAbortSignalを渡す。

概念的には、fetch(url, { signal: controller.signal })という形だ。リクエストを開始したあとでcontroller.abort()を呼び出すと、fetchが返したPromiseが拒否される。このとき、エラーの名前はAbortErrorになる。

処理の流れは、次のように捉えるとわかりやすい。

1. AbortControllerを生成する

2. controller.signalfetchへ渡す

3. ブラウザがリクエストを開始する

4. ユーザー操作やタイムアウトによってabort()が呼ばれる

5. AbortSignalの状態が中断済みに変わる

6. fetchが通知を受け、返却済みのPromiseAbortErrorで拒否される

7. 呼び出し側が中断と本当の通信エラーを分けて処理する

この最後の分類を省くと、ログも画面表示も不自然になる。

ユーザーが検索文字を変更しただけなのに、画面へ「通信に失敗しました」と表示される。ログ監視には大量のエラーが記録される。プロダクト側から見ると、実際には正常なユーザー操作なのに、障害に見えてしまう。

AbortErrorは、ネットワーク障害やサーバーエラーと同じ扱いにしないほうがよい。中断を意図したものとして静かに処理し、それ以外のエラーだけをユーザーへ通知する。これは実装の細部ではなく、ユーザー体験と計測の品質に直結する。

中断と通信エラーを分ける理由

非同期通信で発生するエラーは、少なくとも次のように分類できる。

状態画面上の意味推奨する扱い
AbortError画面や入力の状態が変わり、処理が不要になったエラー表示を避け、必要なら静かに終了
ネットワークエラー接続不能、オフライン、名前解決失敗など再試行や接続確認を案内
HTTPエラーサーバーが4xx・5xxを返したAPI仕様に沿って表示や分岐
JSON解析などの処理エラー応答形式やフロント側の実装に問題があるログへ残し、実装を修正

fetchはHTTPステータスが4xxや5xxであっても、それだけでPromiseを拒否するわけではない。このため、AbortErrorの判定とHTTPステータスの判定は別の層で扱う必要がある。

たとえば、検索画面で候補取得を行う場合、入力が変わったことによる中断は画面に表示しない。一方、APIが認証エラーを返したならログイン状態を確認する。サーバーが一時的に失敗しているなら、再試行を促す。ここを一括して「catchしたらエラー表示」とすると、ユーザーの操作と障害が混ざる。

応答を中断しても、画面の競合は別に残る

AbortControllerを導入すれば、古い通信の問題がすべて消えるわけではない。

リクエストが中断される前に、すでに応答が完了している可能性がある。完了済みのfetchに対して後からabort()を呼び出しても、その結果を巻き戻すことはできない。すでに取得したデータを画面へ反映する処理まで自動的に取り消されるわけでもない。

したがって、検索候補のように結果の順序が重要な画面では、次の対策を組み合わせる必要がある。

  • 前回の通信をabort()して不要な通信を減らす
  • 現在の検索条件やリクエスト識別子を保持する
  • 応答が返った時点で、その結果が現在の画面状態と一致するか確認する
  • 中断された処理を通常のエラー表示から除外する
  • 入力イベントをそのまま送らず、必要に応じて待ち時間を設ける

中断は「古い処理を止める」対策であり、結果の採用条件を決める仕組みではない。この二つは似ているが、責任範囲が違う。

検索フォームでの実装は、入力のたびに通信する設計から見直す

AbortControllerの導入例として、検索フォームはわかりやすい。一方で、検索フォームは実装の問題がユーザーにそのまま見える場所でもある。

入力のたびにAPIを呼ぶ設計では、次のような問題が発生しやすい。

  • 文字入力中の中間状態までサーバーへ送る
  • 通信回数が増え、APIやデータベースの負荷が上がる
  • 古い応答が新しい応答を上書きする
  • モバイル回線で不要な通信が発生する
  • 中断をエラーとして計測し、障害数を誤認する

ここでの仮説は、「最新の入力に対応する通信だけを残せば、画面の不整合と無駄な通信を減らせる」というものだ。

ただし、仮説を実装しただけでは不十分だ。検証では、通信回数だけでなく、候補表示までの時間、入力後の離脱、候補の選択率、APIの処理時間などを見る。プロダクトグロースの観点では、キャンセル数が減ったかよりも、ユーザーが目的の操作を完了できたかが中心になる。

検索処理の状態管理

検索のたびに新しいコントローラーを生成し、前回のコントローラーを破棄する構成が基本になる。実装上は、現在のコントローラーを変数やコンポーネントの状態として保持する。

新しい入力が発生したら、次の順番で処理する。

1. 現在のコントローラーがあれば中断する

2. 新しいコントローラーを生成する

3. 新しいシグナルを使ってAPIを呼び出す

4. 応答を受け取ったあと、画面がまだ同じ検索状態か確認する

5. 中断なら何も表示せず、それ以外の失敗だけをエラー処理する

この順番を逆にして、古いコントローラーを残したまま新しい通信を始めると、リクエストは増え続ける。コントローラーを作っただけでキャンセルが有効になるわけではない。対象のfetchへシグナルを渡しているかが本質だ。

また、コンポーネントが破棄される場合も忘れてはいけない。VueやReactで画面遷移が起きたとき、画面が消えたあとも通信だけが継続すると、不要な処理や状態更新が残る。コンポーネントのアンマウント時にコントローラーを中断する設計は、検索画面以外でも有効だ。

デバウンスとAbortControllerは競合しない

入力後に一定時間待ってから通信するデバウンスは、リクエスト回数を減らす施策だ。AbortControllerは、すでに始まった非同期処理を中断する施策である。

役割が違うため、両方を使える。

  • デバウンス:通信を始める前に、不要な処理を減らす
  • AbortController:通信を始めたあと、不要になった処理を止める
  • 応答の識別:返ってきた結果を採用するか判断する

三つを組み合わせると、入力中の無駄な通信、通信中の不要な処理、応答順序の競合をそれぞれ別の角度から抑えられる。

ただし、待ち時間を長くしすぎると、検索候補の反応が鈍くなる。短すぎると通信回数が減らない。ここはフレームワークの定石で決めるのではなく、実際の入力速度と候補選択率を見ながら調整する領域だ。

一つのAbortSignalで複数リクエストをまとめて止める

AbortController一つに対してAbortSignal一つが存在する。このシグナルを複数のfetchへ渡せば、単一のabort()で複数の通信をまとめて中断できる。

たとえば、商品詳細画面を開いたときに、次のAPIを同時に呼び出すケースがある。

  • 商品本体
  • 在庫情報
  • 関連商品
  • レビュー
  • おすすめ情報

ユーザーがすぐ別の商品へ移動した場合、前の商品に紐づく通信はすべて不要になる。このとき、画面単位でコントローラーを持たせておけば、画面終了時に一括で中断できる。

ここでの設計単位は「リクエスト単位」ではなく「ユーザーが見ている画面や操作単位」になる。ひとつの画面に属する処理をひとつのライフサイクルとして扱う考え方だ。

一括キャンセルで便利な場面

一つのシグナルを複数通信へ渡す設計は、次のような場面で効果を発揮する。

  • 画面遷移によって、表示中のデータがすべて不要になる
  • モーダルを閉じた時点で、内部の候補取得を止めたい
  • 検索条件の変更に伴い、関連する複数のAPIを更新する
  • 認証状態が変わり、古いユーザー向けの通信を破棄したい
  • ページ単位のデータ取得をまとめて管理したい

一方で、すべての通信を同じシグナルへ紐づけるのは危険だ。画面内に表示される広告や分析送信など、ユーザー操作とは別の寿命を持つ通信まで同時に止める可能性がある。

キャンセルの境界は、処理の目的に合わせて設計する。商品詳細の描画に必要な通信と、画面をまたいで継続したい処理は、同じコントローラーにまとめないほうがよい。

コントローラーを使い回さない

AbortControllerは、キャンセル操作のための状態を持つ。一度abort()が実行されると、そのシグナルは中断済みのままだ。

そのため、画面の再読み込みや検索条件の変更で同じ処理を再開する場合、コントローラーも新しく作る必要がある。古いシグナルを渡したままでは、fetchが開始直後にAbortErrorで拒否されることがある。

この挙動は不具合ではない。すでに中断されたシグナルを使っているため、ブラウザがその状態を守っているだけだ。

実装時には、次のような状態を意識すると整理しやすい。

管理対象役割寿命
コントローラー中断を発行するひとつの操作や画面単位
シグナル中断状態を伝えるコントローラーと同じ
リクエストAPI通信の実体通信開始から完了・中断まで
画面状態結果を採用する条件コンポーネントや操作の状態に依存

通信と画面の寿命が一致しているとは限らない。ここを混同すると、画面を閉じたあとに状態更新が走ったり、再利用したシグナルによって新しい処理まで即時中断されたりする。

AbortSignal.timeoutとAbortSignal.anyで中断条件を組み立てる

手動のcontroller.abort()だけでなく、AbortSignal.timeout(ms)を利用すると、指定した時間が経過したあと自動的に中断されるシグナルを生成できる。

APIに応答時間の上限を設けたい場合に使いやすい。ユーザーがボタンを押したあと、いつまでもローディングを表示し続ける状態を避けられる。

ただし、タイムアウトは「サーバーが処理を停止した」という意味ではない。ブラウザ側が応答を待つのをやめたという意味だ。バックエンド側の処理がすでに開始されているなら、その処理が継続する可能性は残る。

タイムアウトを導入したら、フロントエンドだけでなく、APIの処理時間も確認したい。遅いSQL、外部サービスへの依存、画像変換、重い集計などが原因なら、ブラウザの待ち時間を短くするだけでは根本的な改善にならない。

タイムアウト時のエラー分類

タイムアウトによる中断も、通常のユーザー操作による中断とは意味が違う。

  • 入力変更による中断:古い処理が不要になっただけ
  • 画面遷移による中断:表示対象が消えた
  • タイムアウトによる中断:期待した時間内に応答がなかった
  • 手動キャンセル:ユーザーが明示的に停止した

すべてがAbortErrorとして扱われる場合でも、プロダクト上の対応は分けたほうがよい。入力変更なら何も表示しなくてよいが、タイムアウトなら再試行ボタンや、処理に時間がかかっていることを伝える表示が必要になる場合がある。

エラーの技術的な名前だけでなく、中断の理由をアプリケーション側で管理する設計も有効だ。どの操作が中断を発生させたのかを追跡できれば、ログの分析や改善施策の優先順位をつけやすくなる。

AbortSignal.anyで複数の中断条件をまとめる

AbortSignal.any()を使うと、複数のシグナルのうち、いずれか一つが中断されたときに連動する複合シグナルを作成できる。

たとえば、次の条件を同時に成立させたいケースがある。

  • ユーザーが画面を離れたら止める
  • 一定時間を超えたら止める
  • 明示的なキャンセルボタンが押されたら止める

この場合、画面ライフサイクル用のシグナル、タイムアウト用のシグナル、手動操作用のシグナルをまとめ、fetchには複合シグナルを渡す。

設計としては、処理の所有者を一つに固定しないことがポイントだ。画面側は画面側の都合で中断でき、タイマーはタイマーの都合で中断できる。最終的に、どれか一つが発火すれば通信を止める。

ただし、どの理由で中断されたかを画面側で知りたい場合は注意が必要だ。複合シグナルを作るだけでは、プロダクト上の意味まで自動的に整理されない。中断理由を別途保持するか、操作ごとに状態を記録しておく必要がある。

ブラウザ対応と実行環境の差

AbortController自体は現在のWeb開発で広く使える標準APIだが、AbortSignal.timeout()AbortSignal.any()のような比較的新しい機能は、対象ブラウザや実行環境の対応状況を確認したい。

特に、次のような構成では確認箇所が増える。

  • 古いブラウザをサポートしている
  • WebView上で動作する
  • サーバーサイドレンダリングを利用する
  • Node.js側でも同じコードを共有する
  • テスト環境が実ブラウザと異なる

フロントエンドのビルドが通ることと、実際のユーザー環境で動くことは別問題だ。対応していない環境向けには、手動のタイマーとAbortControllerを組み合わせる方法なども候補になる。ただし、ポリフィルを追加するだけで済ませず、キャンセルの意味が同じになるかを確認する必要がある。

AxiosやLaravel APIでも中断の境界を見失わない

AbortControllerfetch専用の考え方ではない。Axiosでも、リクエスト設定にsignalを渡して中断を扱える。既存のAxiosベースのAPIクライアントを大きく置き換えず、キャンセルの制御だけを導入できる。

ただし、通信ライブラリが変わっても、設計上の論点は変わらない。

  • どの操作にリクエストが属しているか
  • いつ不要になるか
  • 中断を画面へどう伝えるか
  • 中断と障害をどう分けるか
  • サーバー側の処理をどう管理するか

Axiosへ切り替えれば自動的に安全になるわけではない。fetchでもAxiosでも、シグナルを渡していなければ中断指示は対象リクエストへ届かない。さらに、catchで全エラーを同じように扱えば、ユーザーの自然な操作がエラー表示へ変わる。

Laravel APIとの連携では、特にサーバー側の処理境界を意識したい。ブラウザが通信を中断したからといって、Laravelのコントローラー内で開始された処理や、データベースの更新が自動的にロールバックされるとは限らない。

たとえば、画面から注文作成のAPIを呼び出したあと、ユーザーが画面を閉じたとする。フロントエンドでabort()を呼び出しても、サーバーがリクエストを受信し、注文作成処理へ進んでいる可能性はある。

このような処理では、キャンセルを単なるネットワーク切断として扱わず、業務上の状態遷移として設計する必要がある。

  • 二重送信を防ぐための冪等性キー
  • 受付済み、処理中、完了、失敗といった状態管理
  • 長時間処理をキューへ分離する設計
  • クライアントからのキャンセル要求を別APIとして扱う方式
  • サーバー側でのタイムアウトとトランザクション境界

読み取り系の検索APIなら、古い通信を中断するだけで十分なケースも多い。一方、決済、注文、会員登録、ファイル変換のような副作用を持つ処理では、ブラウザ側の中断と業務処理の取り消しを同じものとして設計してはいけない。

ブラウザのabortは通信の終了であって、サーバー処理のロールバック命令ではない。ここを分けるだけで、API設計の事故はかなり減る。

UIの中断制御は、ユーザーの待ち時間をどう扱うかという問題

AbortControllerの導入は、コードの短縮や通信数の削減だけを目的にすると効果を見失いやすい。

本当に改善したいのは、ユーザーが「もう終わった」と判断して離脱する瞬間だ。検索結果が古い、画面が固まる、ボタンを押したのか分からない、画面遷移後も前の処理が残る。これらはすべて、非同期処理の状態がユーザーの認識とずれている状態である。

施策の評価では、次の指標を組み合わせると状況を見やすい。

  • APIリクエスト数と中断数
  • 入力から結果表示までの時間
  • 検索候補の選択率
  • リクエスト完了後の画面更新率
  • 中断を除いた実通信エラー数
  • 画面遷移後に発生した状態更新の件数
  • タイムアウト後の再試行率

中断数が増えたからといって、必ずしも悪いわけではない。ユーザーが素早く入力を続けるようになり、古い検索を適切に捨てられている可能性もある。逆に、中断数が減っても、入力後の応答が遅くなり、ユーザーが候補表示前に離脱しているなら改善とは言いにくい。

見るべきなのは、キャンセルという技術イベントではなく、その前後にあるユーザー行動だ。

実装前後で比較するポイント

導入前後の比較では、通信処理だけでなく、画面の状態を追う。

観測対象導入前に起こりやすい状態導入後に確認したい状態
古い応答最新の入力結果を上書きする不要な通信を中断し、結果の採用条件も守る
通信エラー表示意図した中断までエラー扱いになるAbortErrorを通常障害から分離する
API負荷入力や画面遷移後も通信が残る不要になった処理を早めに止める
ローディング表示画面を離れても残る画面状態と通信状態を連動させる
サーバー処理クライアント中断後も処理が続く必要に応じて冪等性やジョブ管理を導入する
ユーザー体験結果が遅い、古い、操作が不安定最新の意図に近い結果を安定して表示する

この比較をしておくと、「AbortControllerを入れたので完了」という誤解を避けられる。実装は施策の開始地点であり、成果ではない。

中断される処理と、完了させるべき処理を分ける

画面を閉じたらすべてを中断する設計が正しいとは限らない。

検索候補、一覧の絞り込み、プレビュー画像の取得は、画面が不要になれば止めやすい。一方、注文確定や監査ログの送信などは、画面が変わったからといって簡単に止めるべきではない。

同じAPI通信でも、副作用の有無によって扱いを変える。

  • 再取得可能な読み取り:中断しやすい
  • 現在の画面にしか意味がない取得:画面単位で中断する
  • 状態を変更する書き込み:中断と業務処理を分離する
  • 長時間の処理:ジョブ化し、進捗とキャンセルを別設計にする

この分類を先に行うと、AbortControllerを導入する場所が見えてくる。すべてのリクエストに機械的に付けるのではなく、「不要になったら止めてもビジネス上の意味が壊れない通信」から始めるのが現実的だ。

AbortControllerを導入するときに起きやすい失敗

実装でよく起きる失敗は、APIの使い方を間違えることより、処理の所有者を決めないことだ。

コントローラーを生成するだけで終わる

AbortControllerを生成しても、fetchへシグナルを渡さなければ中断は起きない。コントローラーとリクエストが接続されていることが必要だ。

また、シグナルを渡していても、別の箇所で新しいfetchを実行しているなら、その通信は中断対象にならない。APIクライアントを共通化している場合は、シグナルが関数の引数を通じて最終的な通信処理まで届いているか確認する。

中断をエラー画面へ流してしまう

catchの中で常にトーストやダイアログを表示すると、入力変更のたびにエラーが出る。ユーザーから見れば、普通に文字を入力しているだけなのに、画面が失敗を訴えてくる。

AbortErrorを中断として扱い、通常の障害ログやエラー表示から切り離す。必要であれば、開発時のデバッグログだけに残す。

中断すればサーバーも止まると思い込む

これは最も危険な誤解だ。ブラウザとサーバーの間の接続が切れても、サーバー側で受信済みの処理が継続することはある。

特に副作用のあるAPIでは、フロントエンドのキャンセルだけで整合性を守れない。重複登録や状態不整合を防ぐには、バックエンド側で別の設計が必要になる。

共通コントローラーに処理を詰め込みすぎる

アプリケーション全体で一つのコントローラーを使い回すと、ある画面の操作が別の通信まで止める可能性がある。キャンセルの単位は、画面、操作、ユーザーセッションなど、処理の目的に応じて決める。

完了済みの通信まで取り消せると思う

fetchがすでに解決または拒否されたあとにabort()を呼び出しても、完了した結果は巻き戻らない。応答を受け取った後の状態更新やデータ反映は、アプリケーション側の責任で管理する。

検索のような競合が起きる画面では、キャンセルに加えてリクエストの世代番号や検索条件の比較を使う。通信を止める仕組みと、結果を採用する仕組みを分けることが安定運用につながる。

Laravel・Vue・Reactの境界で設計を確認する

Laravelをバックエンドに、VueやReactをフロントエンドに使う構成では、AbortControllerは比較的導入しやすい。ただし、コンポーネントのライフサイクルとAPIの責任範囲を接続する必要がある。

Vueなら、コンポーネントが破棄されるタイミングで現在の通信を中断する。Reactなら、エフェクトのクリーンアップ処理で中断する。どちらの場合も、通信処理を開始した場所と、不要になったことを検知する場所を同じライフサイクルの中で管理する。

APIクライアントを別ファイルへ切り出す場合は、シグナルを隠しすぎないほうがよい。画面側がキャンセルしたいのに、APIクライアントが内部でコントローラーを生成してしまうと、画面から中断できなくなる。

設計としては、呼び出し側が必要に応じてシグナルを渡せる形が扱いやすい。

  • APIクライアントは受け取ったシグナルを通信へ渡す
  • コンポーネントは自分の寿命に合わせてコントローラーを管理する
  • 中断理由の分類は画面またはユースケース層で行う
  • Laravel側は通信切断と業務処理の完了を別状態として扱う

TypeScriptを使う場合は、API呼び出し関数の引数としてシグナルを明示すると、キャンセル対応の漏れを見つけやすい。通信関数がどの操作に属しているかも型や命名で表現できる。

ここでも、技術を先に複雑化しないことが重要だ。AbortSignalをどこにでも渡し、すべての処理をキャンセル可能にすることがゴールではない。ユーザーの操作に対して、不要になった処理だけを安全に手放すことが目的だ。

まとめ:キャンセルは速度改善ではなく、状態の整合性を守る施策

AbortControllerの仕組みはシンプルだ。AbortControllerが中断を発行し、AbortSignalがその状態を伝える。fetchはシグナルを監視し、中断を受けるとAbortErrorPromiseを拒否する。

一つのシグナルを複数の通信へ渡せば、画面単位の一括キャンセルもできる。AbortSignal.timeout()を使えば時間制限を設けられ、AbortSignal.any()を使えば画面遷移、タイムアウト、手動操作といった複数の中断条件を組み合わせられる。

ただし、これらはブラウザ側の通信制御である。サーバーへ到達した処理やデータベースのトランザクションまで自動的に巻き戻すものではない。副作用を持つAPIでは、冪等性、状態遷移、ジョブ管理を別に設計する必要がある。

私がこの仕組みを導入するときに最初に置く仮説は、「通信を止めれば速くなる」ではない。「ユーザーの意図と、画面に残る非同期処理のズレを減らせる」だ。

検証する指標も、単純なリクエスト数だけでは足りない。古い結果の表示、候補選択率、タイムアウト後の離脱、意図しないエラー表示。ユーザーの反応を見ながら、どの通信を止め、どの通信を完了させるべきかを決める。

次に試したいことは、キャンセル理由をプロダクトのイベントとして扱うことだ。入力変更による中断と、タイムアウトによる中断を同じAbortErrorのまま集計せず、ユーザーが何をしようとしていたのかまで追跡する。そこまで見えてくると、AbortControllerは単なるWeb APIではなく、非同期UIをユーザーの行動へ近づけるための実験道具になる。

Related reading: Axiosインターセプターによるトークンリフレッシュ:非同期リクエストが並行処理される内部挙動 and Fetch APIとAxiosの選択基準:現代のフロントエンド開発における機能差と移行の判断点.

よくある質問

AbortControllerは何を中断できますか?
AbortControllerが中断するのは、ブラウザ側から見たリクエストなどの非同期処理です。サーバーへ到達して実行中のデータベース処理やバックエンドのトランザクションまで自動的に取り消すものではありません。
fetchでAbortControllerを使う方法は?
AbortControllerを生成し、controller.signalをfetchのRequestInitにあるsignalプロパティへ渡します。通信開始後にcontroller.abort()を呼び出すと、fetchのPromiseはAbortErrorとして拒否されます。
AbortErrorは通常の通信エラーと同じように扱うべきですか?
同じように扱わないほうがよいです。入力変更や画面遷移による意図した中断は、通常のエラー表示や障害ログから分け、ネットワークエラーやHTTPエラーとは別に処理します。
AbortControllerで古い検索結果の表示を完全に防げますか?
AbortControllerだけでは完全には防げません。中断前に応答が完了する可能性があるため、現在の検索条件やリクエスト識別子を確認し、画面状態に一致する結果だけを採用する必要があります。
AbortControllerの同じシグナルを複数のfetchに渡せますか?
渡せます。一つのAbortSignalを複数のfetchへ渡すと、単一のabort()で関連する通信をまとめて中断できますが、異なる寿命を持つ処理まで同じシグナルにまとめないよう注意が必要です。

参考情報