2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScriptにおける非同期キャンセル設計:AbortController、競合制御、UIと連動する中断構造の設計

Posted at

概要

非同期キャンセルとは「中断できるようにする」ことではない。
それは**“UI・ユーザー行動・アプリ状態に連動して、不要な非同期処理を明確に終了させ、意図通りの動作を保証するための制御構造”**である。

ユーザーが入力を変えた瞬間、ページを遷移した瞬間、送信ボタンを押した直後など、キャンセルされるべき非同期は無数に存在する
JavaScriptでは AbortController を用いることで、構造的なキャンセル可能設計が可能になる。


1. AbortController の基本構造

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

fetch('/api/data', { signal })
  .then((res) => res.json())
  .catch((err) => {
    if (err.name === 'AbortError') {
      console.log('fetch aborted');
    }
  });

controller.abort(); // 中断
  • signal を fetch に渡すことでキャンセル可能に
  • ✅ 中断時は AbortError が投げられる

2. UIと連動するキャンセル設計(例:検索フォーム)

let controller: AbortController;

async function fetchSearch(query: string) {
  if (controller) controller.abort(); // 既存リクエスト中断
  controller = new AbortController();

  try {
    const res = await fetch(`/api/search?q=${query}`, {
      signal: controller.signal,
    });
    const data = await res.json();
    showResults(data);
  } catch (err) {
    if (err.name === 'AbortError') return;
    console.error('検索失敗:', err);
  }
}
  • ✅ 入力変化ごとに前回リクエストをキャンセル
  • ✅ UIの連動によって「今必要なものだけ」を残す構造に

3. コンポーネントライフサイクルと連動(React/Vue等)

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

  fetch('/api/data', { signal: controller.signal });

  return () => controller.abort(); // アンマウント時に中断
}, []);
  • ✅ コンポーネントが破棄されてもfetchが動き続けることを防止
  • ✅ 副作用の生存期間を明示的に制御する設計が重要

4. 複数非同期のキャンセル制御(競合防止)

const createCancelableTask = () => {
  let controller = new AbortController();
  return {
    run: (url: string) => {
      controller.abort();
      controller = new AbortController();
      return fetch(url, { signal: controller.signal });
    },
  };
};

const task = createCancelableTask();
task.run('/api/a');
task.run('/api/b'); // 前回はキャンセルされる
  • ✅ 複数リクエストが最後の1つ以外不要な場合に有効

5. 状態とUIに「中断」を反映する設計

const [loading, setLoading] = useState(false);
const [aborted, setAborted] = useState(false);

const fetchData = async () => {
  const controller = new AbortController();
  setLoading(true);
  try {
    const res = await fetch('/api/data', { signal: controller.signal });
    await res.json();
    setAborted(false);
  } catch (e) {
    if (e.name === 'AbortError') {
      setAborted(true);
    }
  } finally {
    setLoading(false);
  }
};
  • UI状態として「中断された」ことも反映すべき
  • ✅ ローディングだけではなく「結果が無効化された」ことをUIで扱う

設計判断フロー

① 非同期処理が「ユーザー操作によって不要になる可能性」があるか?

② 現在の非同期処理が「今のUI」に関連しているか? → スコープの明示

③ キャンセル時に状態破壊やエラー発火が起きていないか?

④ 複数非同期を扱うとき、競合や古いレスポンス処理が混入していないか?

⑤ UIが「キャンセルされた状態」を反映できる構造になっているか?

よくあるミスと対策

❌ キャンセルしない fetch が UI 破棄後に状態を更新

→ ✅ AbortControlleruseEffect の return で中断


❌ 前回の検索が遅延して結果が上書きされる

→ ✅ キャンセル可能設計 or タスク番号で一意化


❌ キャンセルされたリクエストもローディング中扱いになる

→ ✅ 「中断された」ことをUI状態で反映


結語

非同期のキャンセルとは「止めること」ではない。
それは**“不要な処理を明確に切り捨て、今必要なものだけを残す、ユーザー体験と整合性のための制御構造”**である。

  • AbortControllerで非同期を構造化し
  • UIやライフサイクルと連動して中断制御し
  • 今表示すべきデータ・状態を明示的に守る

JavaScriptにおける非同期キャンセル設計とは、
“中断を制御し、整合性を保証することでUXと安全性を両立する戦略”である。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?