JavaScript Tips | 基本・共通ユーティリティ:安全処理 – try-catch ラッパー

JavaScript JavaScript
スポンサーリンク

なぜ「try-catch ラッパー」が業務で効いてくるのか

まず前提として、JavaScript の try-catch は「例外が投げられても、アプリ全体を落とさずに処理を続けるための仕組み」です。

try {
  riskyOperation();
} catch (err) {
  console.error("エラーが発生しました", err);
}
JavaScript

ただ、これをあちこちにベタ書きしていくと、コードがこうなります。

try { ... } catch (e) { ... }
try { ... } catch (e) { ... }
try { ... } catch (e) { ... }
JavaScript

同じパターンが何度も出てきて、
「毎回同じようなエラーログ」「毎回同じようなフォールバック処理」を書くことになります。

ここで登場するのが「try-catch ラッパー」です。
ざっくり言うと、

「危ない処理を渡すと、“成功か失敗か”を分かりやすい形で返してくれる小さなユーティリティ」

です。
これを一つ用意しておくと、業務コードのあちこちで「安全に実行する」というパターンを、きれいに共通化できます。


基本形:同期処理用の try-catch ラッパー

成功・失敗をオブジェクトで返すパターン

まずは、同期処理(普通の関数)を対象にした、いちばん基本的なラッパーです。

function tryCatch(fn) {
  try {
    const value = fn();
    return { ok: true, value };
  } catch (error) {
    return { ok: false, error };
  }
}
JavaScript

使い方はとてもシンプルです。

const result = tryCatch(() => {
  // ここに「落ちるかもしれない処理」を書く
  JSON.parse('{"id":1}');
});

if (result.ok) {
  console.log("成功:", result.value);
} else {
  console.error("失敗:", result.error);
}
JavaScript

ここで重要なのは、「例外を投げる関数」を「例外を投げない関数」に変換している、という発想です。

元の世界
「呼ぶと例外が飛ぶかもしれない関数」

ラッパー後の世界
「呼ぶと { ok: true/false, value/error } を返すだけの関数」

これによって、呼び出し側は try-catch を書かずに、「結果を見て分岐する」だけで済むようになります。


デフォルト値を返す try-catch ラッパー

「失敗したらこの値でいい」という場面

「エラーの詳細はいらないから、失敗したらデフォルト値でいい」という場面も多いです。
その場合は、次のようなラッパーが便利です。

function tryCatchOr(fn, defaultValue) {
  try {
    return fn();
  } catch {
    return defaultValue;
  }
}
JavaScript

使い方の例です。

const value1 = tryCatchOr(
  () => JSON.parse('{"id":1}'),
  {}
);

const value2 = tryCatchOr(
  () => JSON.parse("壊れたJSON"),
  {}
);

console.log(value1); // { id: 1 }
console.log(value2); // {}(失敗したのでデフォルト)
JavaScript

ここでのポイントは、「呼び出し側が try/catch を書かなくてよくなる」ことと、
「失敗時の振る舞い(デフォルト値)を、呼び出し側が明示できる」ことです。


非同期処理(Promise / async)用の try-catch ラッパー

async/await 版の try-catch ラッパー

業務では、非同期処理(API 呼び出し、DB アクセスなど)が多いので、
async 関数や Promise に対応したラッパーもほぼ必須です。

async function tryCatchAsync(fn) {
  try {
    const value = await fn();
    return { ok: true, value };
  } catch (error) {
    return { ok: false, error };
  }
}
JavaScript

使い方の例です。

async function fetchUser() {
  const res = await fetch("/api/user");
  if (!res.ok) throw new Error("HTTP エラー");
  return res.json();
}

(async () => {
  const result = await tryCatchAsync(fetchUser);

  if (result.ok) {
    console.log("ユーザー:", result.value);
  } else {
    console.error("取得失敗:", result.error);
  }
})();
JavaScript

ここでも、「例外を投げるかもしれない async 関数」を、
「常に { ok, value, error } を返す関数」に変換しています。

非同期版の tryCatchOr

「失敗したらこの値でいい」という非同期版もよく使います。

async function tryCatchAsyncOr(fn, defaultValue) {
  try {
    return await fn();
  } catch {
    return defaultValue;
  }
}
JavaScript

使い方です。

const user = await tryCatchAsyncOr(
  fetchUser,
  { id: null, name: "ゲスト" }
);
JavaScript

これで、「API が落ちても、最低限のデフォルトユーザーで画面を描画する」といった振る舞いを簡単に書けます。


「ラッパーを返すラッパー」にするパターン

関数を包んで「安全版関数」を作る

毎回 tryCatch(() => fn()) と書くのが面倒な場合、
「関数を受け取って、安全版の関数を返す」ユーティリティにしてしまう手もあります。

function wrapTryCatch(fn) {
  return function wrapped(...args) {
    try {
      const value = fn(...args);
      return { ok: true, value };
    } catch (error) {
      return { ok: false, error };
    }
  };
}
JavaScript

使い方です。

function parseJson(text) {
  return JSON.parse(text);
}

const safeParseJson = wrapTryCatch(parseJson);

const r1 = safeParseJson('{"id":1}');
const r2 = safeParseJson("壊れたJSON");

console.log(r1.ok, r1.value);  // true, { id: 1 }
console.log(r2.ok, r2.error);  // false, Error(...)
JavaScript

この形にしておくと、「危ない関数を全部“安全版”に差し替える」という設計がしやすくなります。

非同期版も同じ発想で書けます。

function wrapTryCatchAsync(fn) {
  return async function wrapped(...args) {
    try {
      const value = await fn(...args);
      return { ok: true, value };
    } catch (error) {
      return { ok: false, error };
    }
  };
}
JavaScript

どこまで「安全」にするかを決める視点

try-catch ラッパーを設計するとき、次のような軸で考えると整理しやすいです。

エラー情報を返したいか(スタックトレースなど)
失敗時にデフォルト値を返したいか
同期・非同期どちらを対象にするか
関数をその場で実行するか、「安全版関数」を返すか

例えば、

「バッチ処理で、1 レコードごとに成功・失敗を集計したい」
{ ok, value, error } 形式が向いている

「画面表示用に、“ダメならデフォルト値でいい”という軽い処理」
tryCatchOr / tryCatchAsyncOr が向いている

といった感じで使い分けられます。


実務での具体的な利用イメージ

ログ出力や JSON 処理と組み合わせる

以前やった「安全な JSON parse / stringify」と try-catch ラッパーは相性抜群です。

例えば、safeJsonParse を自前で書かずに、try-catch ラッパーで表現することもできます。

function safeJsonParse(text) {
  return tryCatch(() => JSON.parse(text));
}
JavaScript

あるいは、「ログ出力時に絶対落ちたくない」というときに、
safeJsonStringify と組み合わせてこう書けます。

function logDebug(label, payload) {
  const jsonResult = tryCatch(() => JSON.stringify(payload));

  if (jsonResult.ok) {
    console.debug(label, jsonResult.value);
  } else {
    console.debug(label, "[unserializable]", jsonResult.error.message);
  }
}
JavaScript

UI イベントハンドラを安全にする

ボタンクリックなどのイベントハンドラで例外が飛ぶと、
フレームワークによっては画面が壊れたり、以降の処理が動かなくなったりします。

そこで、「イベントハンドラを安全版で包む」というパターンもよく使います。

function wrapHandler(handler) {
  return function safeHandler(event) {
    const result = tryCatch(() => handler(event));
    if (!result.ok) {
      console.error("イベント処理中にエラー:", result.error);
    }
  };
}

// 使用例(仮想的な UI フレームワーク)
button.onclick = wrapHandler(() => {
  riskyOperation();
});
JavaScript

小さな練習で感覚をつかむ

次のような「たまに落ちる関数」を自分で用意して、
tryCatch, tryCatchOr, tryCatchAsync, wrapTryCatch などを試してみてください。

function sometimesFail() {
  if (Math.random() < 0.5) {
    throw new Error("たまたま失敗");
  }
  return "成功";
}
JavaScript

この関数をそのまま呼んだ場合と、try-catch ラッパー経由で呼んだ場合で、
「呼び出し側のコードがどれくらいスッキリするか」「例外にどう向き合えるか」を比べてみると、
「try-catch ラッパーをユーティリティとして持つ意味」が体感として分かってきます。

タイトルとURLをコピーしました