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;
}

全体像を把握するには関連書籍も有用。

関連用語