概要
非同期キャンセルとは「中断できるようにする」ことではない。
それは**“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 破棄後に状態を更新
→ ✅ AbortController
と useEffect
の return で中断
❌ 前回の検索が遅延して結果が上書きされる
→ ✅ キャンセル可能設計 or タスク番号で一意化
❌ キャンセルされたリクエストもローディング中扱いになる
→ ✅ 「中断された」ことをUI状態で反映
結語
非同期のキャンセルとは「止めること」ではない。
それは**“不要な処理を明確に切り捨て、今必要なものだけを残す、ユーザー体験と整合性のための制御構造”**である。
- AbortControllerで非同期を構造化し
- UIやライフサイクルと連動して中断制御し
- 今表示すべきデータ・状態を明示的に守る
JavaScriptにおける非同期キャンセル設計とは、
“中断を制御し、整合性を保証することでUXと安全性を両立する戦略”である。