概要
JavaScriptは非同期を本質に持つ言語である。
イベントループ、非同期I/O、UI描画の最適化など、
その基盤は「同期的に見える非同期制御」に支えられている。
本稿では、非同期処理の設計におけるPromise / async/await / AbortControllerの役割と、それらを組み合わせた制御戦略を提示する。
単なる文法ではなく、**「どう制御するか」「どう失敗させるか」「どう管理するか」**の視点で解説していく。
1. Promise設計の基本と型構造
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve('data'), 1000);
});
}
- ✅ Promiseは「状態管理オブジェクト」:pending → fulfilled / rejected
- ✅ 複数ステップをthenチェーンで接続可能
- ❌ 可読性が低下しやすく、エラーハンドリングが錯綜しやすい
2. async/awaitによる同期的表現
async function loadData() {
try {
const data = await fetchData();
console.log(data);
} catch (e) {
console.error('Failed:', e);
}
}
- ✅ Promiseの状態を同期的に待つ構文 → コードの線形性が向上
- ✅
try/catch
によるエラー制御が可能 - ✅ Promiseとの互換性を維持しつつ、コードの明瞭さが増す
3. AbortControllerによるキャンセル可能な非同期制御
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal });
// キャンセル
controller.abort();
- ✅
fetch
を中断可能にする標準機能 - ✅ UIイベントやルーティング変更時の 非同期キャンセル に有効
- ✅
AbortError
による専用のハンドリングが必要
try {
const res = await fetch(url, { signal });
} catch (e) {
if (e.name === 'AbortError') {
// 明示的なキャンセル
} else {
throw e;
}
}
4. 非同期エラー設計と再試行戦略
async function withRetry(fn, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (e) {
if (i === retries - 1) throw e;
}
}
}
- ✅ **一時的な障害(ネットワーク、API制限)**に耐性を持たせる
- ✅ 非同期処理には失敗を前提とした制御構造が必要
5. 非同期の構造化と可視性の確保
モジュールで切り出す
// userService.ts
export async function getUser(id: string, signal?: AbortSignal) {
const res = await fetch(`/api/users/${id}`, { signal });
if (!res.ok) throw new Error('Fetch failed');
return await res.json();
}
UI側で制御
const controller = new AbortController();
getUser('123', controller.signal)
.then(renderUser)
.catch(handleError);
- ✅ 非同期関数は明確な責務でモジュール化
- ✅ UI側でキャンセル、タイムアウト、再試行を統括
設計判断フロー
① 非同期処理はPromiseであるべきか? → YES → then / async/await に展開
② 処理の順序性が重要か? → async/await で明示
③ ユーザー操作で中断する必要があるか? → AbortControllerを導入
④ エラーは想定されるか? → try/catch + 専用ハンドラ設計
⑤ 再試行・リカバリが必要か? → withRetry等の高階関数で制御
よくあるミスと対策
❌ 非同期処理をtry/catchで囲っていない
→ ✅ async関数内では常にcatchを意識した構造にする
❌ キャンセルできないfetchを多数同時発火
→ ✅ AbortControllerで外部制御可能に設計
❌ UIコンポーネントがアンマウント後にawaitが残ってエラー
→ ✅ useEffect
+ isMounted
or AbortController
で終了制御
結語
非同期制御は「待つ」だけではない。
それは**“実行の責任とタイミングを設計し、失敗を制御可能にする戦略”**である。
- async/await で直感的に順序を表現
- AbortController でキャンセル性を持たせる
- 高階関数で再試行やタイムアウト制御を汎用化
- 非同期処理は責務を分離し、構造的に管理する
JavaScriptにおける非同期制御とは、
“動的で不確実なプロセスを予測可能に設計し、ユーザー体験と安定性を支えるアーキテクチャ戦略である。”