非同期プログラミング

I/O 待ちの間に他の処理を進め、システムのスループットを向上させるプログラミング手法

プログラミングパフォーマンス

非同期プログラミングとは

非同期プログラミングは、I/O 待ち (DB クエリ、API 呼び出し、ファイル読み込み) の間に他の処理を進め、スループットを向上させる手法である。同期処理が「待つ」のに対し、非同期処理は「待たずに次へ進む」。

同期 vs 非同期

同期:
  [DB クエリ 100ms][待ち...][API 呼び出し 200ms][待ち...] → 合計 300ms

非同期 (Promise.all):
  [DB クエリ 100ms    ]
  [API 呼び出し 200ms ] → 合計 200ms (並行実行)

async/await

// ❌ 直列実行: 300ms
const user = await getUser(id);       // 100ms
const orders = await getOrders(id);   // 200ms

// ✅ 並行実行: 200ms
const [user, orders] = await Promise.all([
  getUser(id),
  getOrders(id),
]);

コールバック → Promise → async/await の進化

// コールバック地獄 (2010年代前半)
getUser(id, (err, user) => {
  getOrders(user.id, (err, orders) => {
    getProducts(orders[0].id, (err, products) => {
      // ネストが深くなる...
    });
  });
});

// Promise チェーン (ES2015)
getUser(id)
  .then(user => getOrders(user.id))
  .then(orders => getProducts(orders[0].id));

// async/await (ES2017) - 同期的に読める
const user = await getUser(id);
const orders = await getOrders(user.id);
const products = await getProducts(orders[0].id);

エラーハンドリング

// try-catch で同期的にエラーを捕捉
try {
  const user = await getUser(id);
} catch (error) {
  console.error('Failed to get user:', error);
}

// Promise.allSettled: 一部が失敗しても全結果を取得
const results = await Promise.allSettled([
  getUser('1'),
  getUser('invalid'),
]);
// [{ status: 'fulfilled', value: user }, { status: 'rejected', reason: error }]

Lambda での非同期パターン

パターン 用途
同期 (API Gateway → Lambda) リクエスト/レスポンス
非同期 (SQS → Lambda) バックグラウンド処理
イベント駆動 (DynamoDB Streams → Lambda) データ変更の処理

よくある間違い

  • await を忘れて Promise オブジェクトをそのまま使う
  • ループ内で await して直列実行になる
  • エラーハンドリングを忘れて Unhandled Promise Rejection

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

関連用語