概要
非同期処理は JavaScript における“言語的必然”であり、同期的に書かれたように動く非同期コードこそが設計の鍵となる。
本稿では以下の5領域を軸に、非同期制御を構造的に整理する:
- Promiseの構文と連鎖の意味
- async/awaitによる構造化と制御
- 並列・直列処理の設計分岐
- エラーハンドリングの責任設計
- キャンセル・タイムアウト・安全性の担保
1. Promiseの基本構造と連鎖
fetch('/api/user')
.then(res => res.json())
.then(user => updateUI(user))
.catch(err => handleError(err));
-
.then()
は逐次処理の連鎖 -
.catch()
は任意のポイントでエラーを拾う - 非同期は“値の流れ”として設計する
2. async/awaitによる構造化
async function fetchUser() {
try {
const res = await fetch('/api/user');
const user = await res.json();
updateUI(user);
} catch (err) {
handleError(err);
}
}
- ✅ 同期的構文で非同期を制御できる
- ✅ try/catch による構文的な例外制御が可能
- ✅ 可読性とトレース性の向上
3. 並列と直列の戦略的選択
直列処理(逐次)
const a = await fetch('/a');
const b = await fetch('/b');
→ 各処理が終わるまで次に進まない
並列処理(同時)
const [a, b] = await Promise.all([
fetch('/a'),
fetch('/b')
]);
→ ✅ 並列にリクエスト → 全完了後に受け取る
4. エラーハンドリング戦略
.catch()
/ try-catch
は責任の境界
- 各関数ごとにtry/catchを書く必要はない
- 一貫して“呼び出し元で責任を持つ”か、“関数内で握り潰す”かを設計する
async function riskyOperation() {
const res = await fetch('/x');
if (!res.ok) throw new Error('Failed');
return res.json();
}
// 呼び出し元で責任を持つ
try {
const data = await riskyOperation();
} catch (e) {
notifyUser(e.message);
}
5. タイムアウトとキャンセル設計
タイムアウト実装
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
);
return Promise.race([promise, timeout]);
}
→ ✅ 最も早く解決されたPromiseを採用
AbortControllerによるキャンセル設計
const controller = new AbortController();
const signal = controller.signal;
fetch('/api/data', { signal })
.catch(err => {
if (err.name === 'AbortError') {
console.warn('Request cancelled');
}
});
controller.abort();
→ ✅ DOM標準のキャンセル設計(Fetch API対応)
設計判断フロー
① 複数非同期を順に実行? → await を逐次で使う
② 複数非同期を同時に実行? → Promise.all()
③ 途中で失敗しても残りを実行したい? → Promise.allSettled()
④ タイムアウトを実装したい? → Promise.race() + setTimeout
⑤ 明示的にキャンセルしたい? → AbortController
⑥ 非同期処理の責任をどこで取る? → 呼び出し元 or 関数内でtry/catch
よくあるミスと対策
❌ await
をループ内で逐次処理してしまう
for (const item of items) {
await process(item); // ❌ 非効率
}
→ ✅ Promise.all(items.map(...))
で並列化できる場合も検討
❌ try/catch を書かずに await
だけ使う
→ ✅ 非同期関数はエラーを返す → 必ずtry/catchか.catch()
で受ける
❌ thenとawaitの混在
await fetch(...).then(...); // ❌ 混乱の元
→ ✅ async関数内では await
に統一する
結語
非同期制御は、JavaScriptにおける“未来をどう待つか”の設計である。
-
Promise
は “未来の値”という契約 -
async/await
は その契約を“構文化”する手段 - タイムアウト・キャンセル・並列化は “制御構造の戦略”
時間をどう扱うかは、設計者の意志に委ねられている。
制御できる非同期は、読みやすく壊れにくい未来を保証する。