概要
非同期処理は「待つ」ことではなく、「壊さずに流す」ことが本質である。
UI・ネットワーク・ユーザー操作など、あらゆる外部依存と共存するために、非同期設計は“構造的な設計行為”である必要がある。
本稿では以下の観点から、モダンな非同期アーキテクチャを構築する:
- Promiseチェーンと責務分離
- async/await構文の設計的使いどころ
- 明示的なtry/catchによる例外管理
- 並列・直列の処理設計
- タイムアウト・キャンセルの戦略
- 非同期処理と状態管理の分離
1. Promiseチェーンの基本と構造化
fetch('/api/user')
.then(res => res.json())
.then(data => updateUI(data))
.catch(err => handleError(err));
- ✅
.then()
で段階的に責務を分離 - ✅
.catch()
で末尾にグローバルエラー処理を配置 - ❌ ネストが深くなる or 複数ルートがあると破綻しやすい
2. async/awaitの設計的導入
async function loadUser() {
try {
const res = await fetch('/api/user');
const data = await res.json();
updateUI(data);
} catch (err) {
handleError(err);
}
}
- ✅ 直線的な記述で構造が読みやすい
- ✅
try/catch
による明示的な境界管理
3. 非同期処理の分割と命名の原則
async function fetchUser(id) {
const res = await fetch(`/api/user/${id}`);
return res.json();
}
async function handleUserLoad(id) {
try {
const user = await fetchUser(id);
updateUI(user);
} catch (e) {
showError('ユーザーの読み込みに失敗しました');
}
}
- ✅ 非同期処理を関数に分割
- ✅ それぞれの関数に名前と責務を与える
4. 並列 vs 直列:Promise.all の責務
// 並列実行
const [user, posts] = await Promise.all([
fetchUser(),
fetchPosts()
]);
- ✅ 非依存タスク →
Promise.all()
で並列に - ✅ 依存あり → 逐次await
→ 依存関係に応じた流れの設計が重要
5. タイムアウト/キャンセルの戦略
✅ Promiseを自作してタイムアウトを制御
function timeout(ms) {
return new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
);
}
const result = await Promise.race([
fetch('/api/data'),
timeout(5000)
]);
→ ✅ 外部APIや通信の**“戻ってこない問題”**に構造的に対応
6. イベント駆動と非同期処理の統合
button.addEventListener('click', async () => {
try {
const data = await fetchData();
render(data);
} catch (err) {
notifyUser('読み込みに失敗しました');
}
});
- ✅ イベントハンドラもasyncにできる
- ✅ UIイベント → async処理 → エラーハンドリングの流れを明示
7. 状態管理と非同期処理の責務分離
async function fetchAndUpdate() {
try {
setLoading(true);
const data = await fetchData();
setData(data);
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
- ✅ **UIの状態(ローディング・成功・失敗)**を明確に分けて管理
- ✅ try/catch/finally構造で一貫性ある制御
設計判断フロー
① 流れが読みにくい? → async/awaitで直列化
② 複数の非同期がある? → 関数ごとに分割して明示
③ 外部通信に依存? → タイムアウト/キャンセルを実装
④ 処理順序に依存あり? → awaitを直列に配置
⑤ UIや状態に関与? → try/catch/finallyで責務分離
よくあるミスと対策
❌ try/catchを書かずにasync処理を放置
→ ✅ async関数内では明示的にエラーハンドリングすべき
❌ 複数awaitを順番に実行して無駄な待機
→ ✅ 依存がなければ Promise.all()
で並列に
❌ ローディング・成功・エラー状態が曖昧
→ ✅ 状態管理の責務を明示して制御する
結語
非同期処理は、ただの構文ではなく“設計構造”である。
- awaitの位置は責任の境界
- try/catchは安全性の保証
- 状態と処理を分けることで、ロジックは壊れなくなる
非同期を設計するとは、“流れを制御可能にし、副作用を閉じ込める”ことである。
その構造こそが、堅牢なアプリケーションの基盤を作る。