Promise/async-await
非同期処理の結果を表現するオブジェクトと、それを同期的な記法で扱うための構文
非同期JavaScript
Promise/async-await とは
Promise は、非同期処理の最終的な完了 (または失敗) とその結果値を表現するオブジェクトである。async/await は Promise を同期的なコードのように記述できる構文糖で、ES2017 で導入された。コールバック地獄 (Callback Hell) を解消し、非同期コードの可読性を劇的に向上させた。
コールバック → Promise → async/await の進化
// ❌ コールバック地獄: ネストが深く、エラーハンドリングが散在
getUser(id, (err, user) => {
if (err) return handleError(err);
getOrders(user.id, (err, orders) => {
if (err) return handleError(err);
getPayment(orders[0].id, (err, payment) => {
if (err) return handleError(err);
console.log(payment);
});
});
});
// ✅ Promise チェーン: フラットだがまだ冗長
getUser(id)
.then(user => getOrders(user.id))
.then(orders => getPayment(orders[0].id))
.then(payment => console.log(payment))
.catch(handleError);
// ✅ async/await: 同期コードのように読める
async function processPayment(id: string) {
const user = await getUser(id);
const orders = await getOrders(user.id);
const payment = await getPayment(orders[0].id);
console.log(payment);
}
Promise の 3 つの状態
pending (保留中) ──→ fulfilled (成功) → .then() で値を取得
└→ rejected (失敗) → .catch() でエラーを取得
一度 fulfilled または rejected になると、状態は変わらない (不変)。
並行実行パターン
// Promise.all: 全部成功したら結果を返す。1 つでも失敗したら即 reject
const [user, orders, config] = await Promise.all([
getUser(id),
getOrders(id),
getConfig(),
]);
// Promise.allSettled: 全部完了するまで待つ (成功・失敗を問わない)
const results = await Promise.allSettled([
sendEmail(user),
sendSlack(user),
sendSms(user),
]);
// results: [{ status: 'fulfilled', value: ... }, { status: 'rejected', reason: ... }]
// Promise.race: 最初に完了した結果を返す (タイムアウトに使える)
const result = await Promise.race([
fetchData(),
timeout(5000), // 5秒でタイムアウト
]);
| メソッド | 成功条件 | 失敗条件 | 用途 |
|---|---|---|---|
Promise.all |
全部成功 | 1 つでも失敗 | 全データが必要な場合 |
Promise.allSettled |
常に成功 | - | 部分的な失敗を許容 |
Promise.race |
最初の完了 | 最初の失敗 | タイムアウト |
Promise.any |
最初の成功 | 全部失敗 | フォールバック |
よくあるバグ
await の付け忘れ
// ❌ await がない: promise オブジェクトが返る (値ではない)
const user = getUser(id); // Promise<User> が返る
console.log(user.name); // undefined (Promise に name プロパティはない)
// ✅ await で値を取り出す
const user = await getUser(id); // User が返る
console.log(user.name); // 正しい値
@typescript-eslint/no-floating-promises ルールで検出できる。
直列実行の非効率
// ❌ 直列: 各 await が前の完了を待つ (遅い)
const user = await getUser(id); // 100ms
const orders = await getOrders(id); // 100ms
const config = await getConfig(); // 100ms
// 合計: 300ms
// ✅ 並行: 独立した処理は Promise.all で同時実行
const [user, orders, config] = await Promise.all([
getUser(id), // 100ms
getOrders(id), // 100ms
getConfig(), // 100ms
]);
// 合計: 100ms (最も遅い処理の時間)
エラーの握りつぶし
// ❌ catch で何もしない → エラーが消える
try { await riskyOperation(); } catch (e) { /* 何もしない */ }
// ✅ エラーをログに記録し、必要なら再スロー
try { await riskyOperation(); } catch (e) {
logger.error('Operation failed', e);
throw e;
}
全体像を把握するには関連書籍も有用。