概要
非同期処理とは「async/awaitで書くこと」ではない。
それは**“処理の流れ・構造・失敗・依存を意図通りに制御し、読みやすさと再利用性を両立させるための構造設計”**である。
JavaScriptにおける非同期処理は、Promise、async/await、thenチェーン、並列実行、順次処理、条件付き非同期など多様なパターンが存在する。
それらを目的に応じて設計・選択しなければ、見通しの悪いコードやバグの温床となる。
1. 非同期制御の基本形:Promise vs async/await
// Promiseチェーン
doA()
.then(doB)
.then(doC)
.catch(handleError);
// async/await
async function execute() {
try {
const a = await doA();
const b = await doB(a);
await doC(b);
} catch (e) {
handleError(e);
}
}
- ✅ 同じ処理でも書き方は2通り
- ✅ async/await は 同期的に見える非同期で可読性が高い
2. 直列処理(順番が大事)
for (const item of items) {
await process(item); // 直列処理
}
- ✅ 順序保証が必要なときは直列が正解
- ❌
forEach + async
は awaitを無視される →注意!
3. 並列処理(速度最優先)
await Promise.all(items.map(item => process(item)));
- ✅ 順序不要で すべて成功する必要があるとき
- ❌ 途中で失敗するとすべてキャンセルされる点に注意
4. 条件付き非同期処理
if (shouldFetch) {
await fetchData();
}
const data = isCached ? getFromCache() : await fetchFresh();
- ✅ 状態や条件に応じて非同期を選択
- ✅ 条件によって 同期/非同期が混在するパターンはテスト観点で要注意
5. レース構造(先に返ってきたものを使う)
const fast = await Promise.race([fromAPI(), fromCache()]);
- ✅ 最も早いレスポンスを使いたい場合に有効
- ❌ タイムアウト / エラーハンドリングとの組み合わせ設計が重要
6. タイムアウト付き非同期処理
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
);
return Promise.race([promise, timeout]);
}
- ✅ ネットワークなど 不確実性の高い処理の安全弁
- ✅
AbortController
と組み合わせるとキャンセルも可能
設計判断フロー
① この非同期は順序が必要か? → 直列設計
② 複数処理は独立しているか? → 並列設計 + Promise.all
③ 条件によって非同期が変化するか? → 分岐を明示的に
④ 早く返ってきたものを採用すべきか? → Promise.race構造
⑤ ネットワーク不安定 or 外部依存があるか? → タイムアウト構造を導入
よくあるミスと対策
❌ forEach に async を使って順番が崩れる
→ ✅ for...of + await に置き換える
❌ すべて並列にしてしまい、エラー原因が特定できない
→ ✅ エラーハンドリングは try-catch
or Promise.allSettled
を活用
❌ 条件で await するかしないかが混乱している
→ ✅ 同期・非同期どちらでも統一的に扱えるよう設計
結語
非同期処理とは「awaitをつければ済む」ものではない。
それは**“実行順、依存関係、例外、時間制約など、あらゆる変数を制御するための設計的選択の集合体”**である。
- 順序と独立性に応じて直列 / 並列を選び
- 条件やレスポンスタイムに応じて制御構造を設計し
- 例外・タイムアウト・キャンセルを含めて全体をデザインする
JavaScriptにおける非同期処理設計とは、
“不確実性を構造で飲み込み、意図を保証するための非同期の設計戦略”である。