「エラー情報の整形」を一言でいうと
「エラー情報の整形」は、
「バラバラで生々しいエラー情報を、“人間やアプリが扱いやすい形” に整理し直すこと」 です。
生のエラーは、たいていこうです。
・メッセージが英語で長い
・スタックトレースがずらっと出る
・API から返ってきた JSON がそのまま入っている
・ユーザーに見せるには情報が多すぎる or 不親切
そこで、
「ログ用にはこう整える」
「ユーザー向けにはこう要約する」
「アプリ内部ではこういう共通フォーマットにする」
といった“変換”を行うのが「エラー情報の整形」です。
ここが重要です。
エラー情報の整形は、
「エラーを隠す」のではなく、「エラーを意味のある形に翻訳する」作業 です。
非同期処理(fetch / Promise / async/await)では、ここをサボると一気にカオスになります。
なぜ「生のエラー」をそのまま使うとつらいのか
生の Error オブジェクトは“開発者向け”すぎる
例えば、単純なエラーを投げてみます。
try {
throw new Error("Something bad happened");
} catch (err) {
console.error(err);
}
JavaScriptコンソールには、メッセージと一緒にスタックトレースがずらっと出ます。
これは開発者にとってはありがたい情報ですが、
ユーザーにそのまま見せるものではありません。
非同期処理になると、さらに情報が増えます。
try {
const res = await fetch("https://example.com/api/data");
if (!res.ok) {
throw new Error("HTTP error " + res.status);
}
} catch (err) {
console.error(err);
}
JavaScriptここで err をそのままユーザーに alert したら、
「HTTP error 500」みたいな、
意味は分かるけど不親切なメッセージになります。
API からのエラーもそのままだと扱いづらい
API がこんな JSON を返してくるとします。
{
"error": {
"code": "VALIDATION_ERROR",
"message": "メールアドレスの形式が正しくありません",
"details": {
"email": "有効なメールアドレスを入力してください"
}
}
}
これをそのまま err に突っ込んでしまうと、
・どこに何が入っているのか毎回バラバラ
・画面ごとにパースの仕方が違う
・ログにもユーザーにも同じ情報を出してしまう
という状態になりがちです。
ここが重要です。
「生のエラー情報」は、“素材” にすぎません。
そのまま使うと、ログも UI もバラバラになり、
どこで何が起きているのか追いにくくなります。
だからこそ、一度“整形レイヤー”を通す価値があるのです。
エラー情報を「共通フォーマット」に整形する
共通フォーマットのイメージ
まず、「アプリ内部で扱うエラー情報の形」を決めてしまうと楽になります。
例えば、こんな形を共通フォーマットと決めるとします。
{
type: "network" | "api" | "validation" | "unknown",
message: "ユーザーに見せられるメッセージ",
rawError: Error や元のレスポンスなど生の情報,
status: HTTP ステータス(あれば),
code: サーバー側のエラーコード(あれば)
}
JavaScriptこの形に「変換」してしまえば、
画面側は「type と message だけ見ればだいたい分かる」状態になります。
整形用の関数を作る
例えば、こんな関数を用意します。
function normalizeError(err) {
// すでに整形済みならそのまま返す
if (err && err.__normalized) {
return err;
}
const normalized = {
type: "unknown",
message: "エラーが発生しました。時間をおいて再度お試しください。",
rawError: err,
status: undefined,
code: undefined,
__normalized: true,
};
// ネットワークエラーっぽいもの
if (err?.name === "TypeError" && err.message === "Failed to fetch") {
normalized.type = "network";
normalized.message = "ネットワークエラーが発生しました。接続を確認してください。";
return normalized;
}
// カスタムエラー(後で出てくる ApiError など)に対応
if (err?.name === "ApiError") {
normalized.type = "api";
normalized.message = err.message;
normalized.status = err.status;
normalized.code = err.code;
return normalized;
}
// それ以外は unknown のまま
return normalized;
}
JavaScriptこの normalizeError は、
「生の err を受け取って、アプリ内で扱いやすい形に変換する」役割を持ちます。
ここが重要です。
「normalize(正規化)」する関数を一つ決めておくと、
どんなエラーが来ても、最終的には同じ形で扱えるようになります。
これが“エラー情報の整形”の中核です。
fetch + カスタムエラー + 整形の流れを具体的に見る
まずカスタムエラーで「意味」を付ける
前の会話で出てきたような ApiError を使います。
class ApiError extends Error {
constructor(message, status, body, code) {
super(message);
this.name = "ApiError";
this.status = status;
this.body = body;
this.code = code;
}
}
JavaScriptfetch をラップして ApiError を投げる
共通の API 呼び出し関数を作ります。
async function callApi(url, options = {}) {
let response;
try {
response = await fetch(url, {
headers: {
"Content-Type": "application/json",
Accept: "application/json",
...(options.headers || {}),
},
...options,
});
} catch (err) {
// ネットワークレベルのエラー(そもそもサーバーに届いていない)
throw err; // ここではまだ整形しない
}
let body = null;
try {
body = await response.json();
} catch (e) {
// JSON でない場合は body は null のままでもよい
}
if (!response.ok) {
const serverMessage = body?.error?.message;
const serverCode = body?.error?.code;
const message =
serverMessage ||
`サーバーでエラーが発生しました(${response.status})`;
throw new ApiError(message, response.status, body, serverCode);
}
return body;
}
JavaScriptここまでで、
・ネットワークエラー → 生の Error
・HTTP エラー → ApiError
という「意味付け」ができました。
呼び出し側で「整形」してから扱う
次に、画面側のコードで normalizeError を使います。
async function loadUsers() {
try {
const body = await callApi("/api/users");
renderUsers(body.data);
} catch (err) {
const e = normalizeError(err);
console.error("ログ用:", e.rawError);
if (e.type === "network") {
showErrorMessage(e.message);
} else if (e.type === "api") {
showErrorMessage(e.message);
} else {
showErrorMessage(e.message);
}
}
}
JavaScriptここでのポイントは、
・ログには e.rawError(生の情報)を出す
・ユーザーには e.message(整形済みのメッセージ)だけを見せる
という役割分担ができていることです。
ここが重要です。
「カスタムエラーで“意味”を付ける」
→ 「normalizeError で“形”を揃える」
→ 「画面側では type と message だけを見て振る舞いを決める」
という三段階の流れができると、非同期エラー処理が一気に整理されます。
ユーザー向けメッセージとログ向け情報を分けて整形する
ユーザーに見せるべき情報はかなり少ない
例えば、サーバーからこんなエラーが返ってきたとします。
{
"error": {
"code": "EMAIL_ALREADY_USED",
"message": "This email is already registered.",
"debug": "UserId=123, RequestId=abc-xyz"
}
}
開発者にとっては全部大事ですが、
ユーザーに「This email is already registered.」と英語で出しても親切ではありません。
そこで、整形の段階で「ユーザー向けメッセージ」を決めます。
function toUserMessage(normalizedError) {
if (normalizedError.type === "network") {
return "ネットワークエラーが発生しました。接続を確認してください。";
}
if (normalizedError.type === "api") {
if (normalizedError.code === "EMAIL_ALREADY_USED") {
return "このメールアドレスは既に登録されています。";
}
return normalizedError.message; // サーバーからのメッセージをそのまま使う or 汎用文言
}
return "エラーが発生しました。時間をおいて再度お試しください。";
}
JavaScriptログには「生の情報+整形後」を両方残す
ログ用には、もう少しリッチな情報を残します。
function logError(normalizedError) {
console.group("アプリケーションエラー");
console.error("type:", normalizedError.type);
console.error("status:", normalizedError.status);
console.error("code:", normalizedError.code);
console.error("message:", normalizedError.message);
console.error("raw:", normalizedError.rawError);
console.groupEnd();
}
JavaScript画面側では、こう使えます。
catch (err) {
const e = normalizeError(err);
logError(e);
const userMessage = toUserMessage(e);
showErrorMessage(userMessage);
}
JavaScriptここが重要です。
エラー情報の整形は、「ユーザー向け」と「ログ向け」を分けることでもあります。
ユーザーには短く分かりやすく、
ログには詳細で生々しく。
その橋渡しをするのが“整形レイヤー”です。
非同期処理ならではの整形ポイント
どの段階で失敗したのかをメッセージに反映する
非同期処理では、失敗ポイントがいくつかあります。
・リクエストを送る前(バリデーション)
・リクエスト送信中(ネットワークエラー)
・レスポンス受信後の HTTP エラー
・レスポンス JSON のパースエラー
・その後のアプリ内処理(データ変換など)のエラー
整形の段階で、「どこで失敗したか」をメッセージに反映できます。
例えば、ApiError に「phase(段階)」を持たせることもできます。
class ApiError extends Error {
constructor(message, status, body, code, phase) {
super(message);
this.name = "ApiError";
this.status = status;
this.body = body;
this.code = code;
this.phase = phase; // "request" | "response" | "parse" など
}
}
JavaScriptこれを使えば、ログに
「レスポンス受信後のパースで失敗」
「レスポンスは来たが、ステータスが 500」
といった情報を残せます。
再試行やリカバリの判断材料にする
整形されたエラー情報を使って、
「このエラーは再試行すべきか?」
「ユーザーに再読み込みボタンを出すべきか?」
といった判断もできます。
例えば、
・type が “network” → 再試行候補
・status が 500〜599 → 再試行候補
・code が “VALIDATION_ERROR” → 再試行しても意味がない(入力を直す必要がある)
といったルールを、整形後の情報に対して書けます。
ここが重要です。
非同期処理では、「エラー情報の整形」がそのまま「次のアクションの判断材料」になります。
整形されたエラーが賢ければ賢いほど、アプリのふるまいも賢くできます。
初心者として「エラー情報の整形」で本当に押さえてほしいこと
生の Error や API レスポンスを、そのまま UI やロジックに使うと、
コードもメッセージもバラバラになりやすい。
まずは「アプリ内で扱うエラーの共通フォーマット」を決める。type, message, status, code, rawError など、
自分のアプリに必要な項目を一つのオブジェクトにまとめる。
カスタムエラー(ApiError など)で「意味」を付け、normalizeError のような関数で「形」を揃える。
画面側は、その整形済みエラーだけを見て振る舞いを決める。
ユーザー向けメッセージとログ向け情報を分ける。
ユーザーには短く分かりやすく、
ログには詳細で生の情報を残す。
ここが重要です。
エラー情報の整形は、「エラーをきれいに隠す」ためではなく、
「エラーを正しく理解し、正しく伝える」ための技術です。
コードを書くときに、
「このエラーを、ユーザーと自分(開発者)にどう見せたいか?」
と一度立ち止まって考えてみてください。
その問いに答える形で、“整形レイヤー” を少しずつ育てていくと、
非同期エラー処理はぐっと落ち着いたものになっていきます。
