async/await のエラーハンドリング - JavaScript

async/await は Promise を同期的なコードのように書ける構文です。エラーハンドリングも try-catch が使えるため、直感的に書けます。

基本的な try-catch

await している処理でエラーが発生すると、通常の例外と同じように catch で捕捉できます。

async function fetchUser(id) {
  try {
    const response = await fetch(`/api/users/${id}`);
    const user = await response.json();
    return user;
  } catch (error) {
    console.error('ユーザー取得エラー:', error.message);
    return null;
  }
}

Promise チェーンの .catch() と比べて、同期コードと同じ感覚で書けるのがメリットです。

finally も使える

try-catch-finally の構文もそのまま使えます。

async function loadData() {
  showLoading();
  try {
    const data = await fetchData();
    displayData(data);
  } catch (error) {
    showError(error);
  } finally {
    hideLoading(); // 必ず実行される
  }
}

エラーの再スロー

catch でログを取った後、上位の呼び出し元にもエラーを伝えたい場合は再スローします。

async function processOrder(orderId) {
  try {
    const order = await fetchOrder(orderId);
    await validateOrder(order);
    await submitOrder(order);
  } catch (error) {
    console.error('注文処理エラー:', error);
    throw error; // 呼び出し元に伝える
  }
}

複数の await とエラー

複数の await がある場合、最初にエラーが発生した時点で catch に移ります。

async function main() {
  try {
    const a = await stepA(); // ここでエラーが起きると
    const b = await stepB(); // ここは実行されない
    const c = await stepC(); // ここも実行されない
    return c;
  } catch (error) {
    console.error('いずれかのステップで失敗:', error);
  }
}

並列実行時のエラー処理

Promise.all を await する場合も、try-catch で捕捉できます。

async function fetchAllData() {
  try {
    const [users, posts] = await Promise.all([
      fetch('/api/users').then(r => r.json()),
      fetch('/api/posts').then(r => r.json())
    ]);
    return { users, posts };
  } catch (error) {
    console.error('データ取得に失敗:', error);
    return { users: [], posts: [] };
  }
}

Promise.all は1つでも失敗すると全体が失敗します。個別にエラーを処理したい場合は Promise.allSettled を使います。

try-catch を使わないパターン

毎回 try-catch を書くのが冗長に感じる場合、呼び出し側で catch する方法もあります。

// 関数側ではエラー処理しない
async function fetchUser(id) {
  const response = await fetch(`/api/users/${id}`);
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }
  return response.json();
}

// 呼び出し側でハンドリング
fetchUser(1)
  .then(user => console.log(user))
  .catch(error => console.error('失敗:', error));

ヘルパー関数を使う

Go 言語風に、エラーと結果をタプルで返すパターンもあります。

async function safeAsync(promise) {
  try {
    const result = await promise;
    return [null, result];
  } catch (error) {
    return [error, null];
  }
}

// 使い方
const [error, user] = await safeAsync(fetchUser(1));
if (error) {
  console.error('エラー:', error);
} else {
  console.log('ユーザー:', user);
}

try-catch のネストを減らしつつ、エラー処理を明示的に行えます。

よくある間違い

async 関数の呼び出しで await を忘れると、エラーが捕捉されません。

async function main() {
  try {
    fetchData(); // await 忘れ!
    console.log('完了'); // すぐに実行される
  } catch (error) {
    // fetchData のエラーは捕捉されない
    console.error(error);
  }
}
await あり

Promise が解決するまで待ち、エラーも捕捉される

await なし

Promise が返るだけで待たない。エラーは未処理になる

async/await を使うときは、await の付け忘れに注意しましょう。