2026年2月7日

Async/Awaitで try-catch を使うべきか?

これは非常にクラシックかつ実用的な問題ですね。

結論から言うと、「通常は必要ですが、唯一の正解ではありません」

async/await を使用する核心的な目的は、非同期コードを同期コードのように見せることにあります。同期コードにおいて例外処理の標準は try...catch です。しかし、すべての await を盲目的に try...catch で囲むと、コードが冗長(スパゲッティ化)になってしまいます。

以下に、「try-catch を書くべきか否か」の詳細な分析とベストプラクティスをまとめました。

1. エラー処理が必須なケース (Must Catch)

エラーを捕捉しないと、Promiseが reject された際に UnhandledPromiseRejectionWarning が発生したり、最悪の場合 Node.js アプリがクラッシュしたり、フロントエンドで画面が真っ白(ホワイトアウト)になったりする可能性があります。

シナリオA:標準的な try...catch の書き方

複数の非同期処理が依存し合っている場合や、一つのブロック内で非同期・同期両方のエラーをまとめて捕捉したい場合に、最も推奨される汎用的な書き方です。

JavaScript

async function getUserData() {
  try {
    // ここで fetch が失敗したり、パースに失敗した場合でも catch で捕捉されます
    const response = await fetch('/api/user');
    const user = await response.json();
    
    // ここでの同期コードのエラー(例:プロパティがない等)さえも捕捉可能です
    console.log(user.name.toUpperCase()); 

  } catch (error) {
    // エラーを一元管理:ユーザーへのポップアップ通知など
    console.error("ユーザー取得失敗:", error);
    showErrorToast(error.message);
  } finally {
    // ローディングアニメーションを停止するなど
    stopLoading();
  }
}

2. try...catch を書かなくても良いケース (混合スタイル)

単純なロジックのために5~6行もの try...catch を書くのは冗長です。await する Promise の後ろに直接 .catch() を繋げる方法があります。

シナリオB:.catch() を使ったフォールバック(兜底)

エラー処理ロジックが非常にシンプル(デフォルト値を返すだけなど)な、単一の非同期操作に適しています。

JavaScript

// 書き方 1: 冗長な try-catch
let user;
try {
  user = await fetchUser();
} catch (e) {
  user = null;
}

// 書き方 2: エレガントな混合スタイル(推奨)
// エラーなら null を返し、コードの実行を中断しない
const user = await fetchUser().catch(err => {
    console.log(err);
    return null; 
});

if (!user) return; // 後続の処理へ

3. “Try-Catch地獄” の解決策

コードが try...catch だらけでネストが深くなる場合、より高度なパターンを試す価値があります。

シナリオC:Go言語スタイル (Await Wrapper)

[error, data] を返すヘルパー関数を作成する人気のパターンです。Go言語のようにエラー処理ができ、インデントが深くなるのを防げます。

JavaScript

// ヘルパー関数
const to = (promise) => {
  return promise
    .then(data => [null, data])
    .catch(err => [err, null]);
}

async function main() {
  // 非常にフラットに見え、try-catch ブロックがありません
  const [err, user] = await to(fetchUser());
  
  if (err) {
    return console.error('エラー発生', err);
  }

  console.log('ユーザー取得成功', user);
}

4. まとめ:いつどれを使うべき?

シナリオ推奨の書き方理由
複雑な業務ロジックtry...catch複数ステップのエラーを一括捕捉でき、ロジックが明確になるため。
単純なAPI呼び出しawait fn().catch()コードが簡潔。1行でデフォルト値やログ処理が完結するため。
トップレベル呼び出し
(React useEffect等)
.catch()トップレベルでは外部にエラーを投げる必要がなく、その場で処理して完結させるため。
ネストを極端に嫌うWrapperパターンコードが極めてフラットで可読性が高いが、追加のユーティリティ関数が必要。

重要なアドバイス

エラーを「握りつぶさ(吞没)」ないこと。

最悪なのは、try...catch を書いたのに catch ブロックで何もしない(あるいはログだけ出して放置する)ことです。これはデバッグ困難なバグの原因になります。

悪い例(アンチパターン):

JavaScript

try {
  await sensitiveOperation();
} catch (e) {
  // 絶対に空にしてはいけません!最低でも console.error か監視ツールへの通知を行いましょう
}