概要
JavaScriptにおける非同期処理には、以下の3つの主要な構文スタイルが存在する:
-
Promise
: 非同期処理の構造化 -
async/await
: 同期風の記述で非同期処理を簡潔に -
Generator
: 処理の“中断と再開”を可能にするイテレーター
それぞれが持つ特性と設計思想は異なり、
「どの構文を使うか」は単なる好みではなく、制御フローの設計選択そのものである。
対象環境
JavaScript(ES6〜ES2023)
Node.js / モダンブラウザ両対応
1. Promise:非同期処理の構造化と連結
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve('data'), 1000);
});
}
fetchData()
.then(result => console.log(result))
.catch(err => console.error(err));
✅ 特徴
-
then()
/catch()
/finally()
によるチェーン処理 - 並列実行 (
Promise.all
) に強い - 可読性は低くなりがち(ネストで複雑化)
2. async/await:同期処理のように非同期を書く
async function load() {
try {
const data = await fetchData();
console.log(data);
} catch (e) {
console.error(e);
}
}
✅ 特徴
- ✅ 見た目が同期的 → 可読性が高い
- ✅ エラーハンドリングも
try/catch
で統一 - ❌ 並列処理は
await
の順番に依存する →Promise.all()
との併用が必要
並列実行したい場合
const [a, b] = await Promise.all([fetchA(), fetchB()]);
3. Generator(+ coライブラリなど)
function* task() {
yield new Promise(resolve => setTimeout(() => resolve('step1'), 500));
yield new Promise(resolve => setTimeout(() => resolve('step2'), 500));
}
-
yield
によって処理を一時停止できる -
for...of
やnext()
により段階的に処理を進行 - 単体では非同期制御は難しく、
co
やredux-saga
などの中間層と併用
async generator + for await...of
async function* steps() {
yield await fetch('...');
yield await fetch('...');
}
for await (const res of steps()) {
console.log(await res.json());
}
構文・設計の違いまとめ
項目 | Promise | async/await | Generator |
---|---|---|---|
見た目の構造 | チェーン構造 | 同期的構造 | 明示的なステップ制御 |
非同期サポート | ✅ | ✅(awaitで内部的にPromise) | ❌(明示的にPromiseをyield) |
並列処理の簡便性 | ✅ | ⚠️ 要 Promise.all()
|
❌ やや難解 |
制御の細かさ | 中程度 | シンプル | 高い(中断・再開) |
ライブラリ併用の必要性 | なし | なし | 必要(co / redux-saga) |
実務での設計判断フロー
① 非同期処理が1回だけ? → async/await
② エラーをtry/catchで管理したい? → async/await
③ 複数Promiseを並列に処理したい? → Promise.all + async/await
④ 制御フローを段階的に記述したい? → Generator + ライブラリ
⑤ 同期・非同期の“混在”処理? → Generatorが最適
よくあるミス
❌ async関数は Promise を返すことを忘れる
async function example() {
return 42;
}
example().then(v => console.log(v)); // 42(Promiseで返ってくる)
❌ await の使いすぎによる逐次化
// ❌ 順番に実行されて非効率
const a = await fetchA();
const b = await fetchB();
// ✅ 並列化
const [a, b] = await Promise.all([fetchA(), fetchB()]);
結語
非同期制御構文は、ただの書き方ではない。
制御の明確さ、エラー処理の一貫性、並列性、再利用性――
それらを設計としてどう選び、組み立てるかが問われている。
-
Promise
は構造化の基盤 -
async/await
は可読性の王者 -
Generator
は制御性の鬼神
構文を知るだけでは設計にはならない。
“選ぶ理由”を理解し、“捨てる理由”を持てることが、設計者の第一歩である。