ユーザー向けエラー表示を一言でいうと
ユーザー向けエラー表示は、
「技術的に何が壊れたか」ではなく、
「ユーザーが次にどうすればいいか」を伝えるためのメッセージ です。
非同期処理(fetch / API 通信)のエラーは、
ネットワーク、サーバー、バリデーション、バグ…と原因がバラバラで、
そのままの情報はほぼ「開発者向け」です。
だから、
生のエラー情報をそのまま alert するのではなく、
「ユーザー向けに翻訳するレイヤー」を必ず一枚挟む。
ここを意識できるかどうかで、アプリの“優しさ”がかなり変わります。
まず「開発者向け」と「ユーザー向け」を分けて考える
生のエラーは開発者のための情報
例えば、fetch でエラーが起きたときの典型的な例です。
try {
const res = await fetch("https://example.com/api/data");
if (!res.ok) {
throw new Error("HTTP error " + res.status);
}
} catch (err) {
alert(err.message);
}
JavaScriptユーザーには「HTTP error 500」などと表示されます。
これは「何か壊れている」ことは伝わりますが、
「自分はどうすればいいのか」が分かりません。
同じ err をコンソールに出すと、
メッセージに加えてスタックトレースが表示されます。
これは完全に開発者向けの情報です。
ここで大事なのは、
「エラーオブジェクトは、まず開発者のための生データ」
という認識です。
ユーザーに見せる前に、必ず「翻訳」が必要になります。
ユーザーが知りたいのは「原因」ではなく「行動」
ユーザーが本当に知りたいのは、
「サーバーが 500 を返した理由」ではなく、
「今は待つべきか」「再読み込みすべきか」「入力を直すべきか」です。
だから、ユーザー向けメッセージは、
原因の詳細よりも、
今何が起きているのか(ざっくり)
ユーザーにできることは何か
を短く伝えることを優先します。
例えば、
「サーバーでエラーが発生しました(500)」ではなく、
「サーバーでエラーが発生しました。時間をおいて再度お試しください。」
という形に整えるイメージです。
非同期エラーを「ユーザー向けメッセージ」に変換する流れ
1. エラーを捕まえる(try / catch や catch)
まずは、非同期処理のエラーをきちんと捕まえます。
async / await なら try / catch、Promise チェーンなら .catch を使います。
async function loadUsers() {
try {
const res = await fetch("/api/users");
if (!res.ok) {
throw new Error("HTTP error " + res.status);
}
const data = await res.json();
renderUsers(data);
} catch (err) {
// ここでユーザー向けに変換する
}
}
JavaScriptここまでは「技術的なエラーを捕まえる」段階です。
この時点の err は、まだ“生”のエラーです。
2. エラーを分類する(ネットワーク / API / バリデーションなど)
次に、「これはどんな種類のエラーか?」をざっくり分類します。
ネットワークエラーなのか、HTTP ステータスエラーなのか、
それとも自分で投げたバリデーションエラーなのか。
カスタムエラーや整形関数を使うと、この分類がやりやすくなります。
ここではシンプルに、メッセージやプロパティで判定してみます。
function toUserFriendlyMessage(err) {
if (err.name === "TypeError" && err.message === "Failed to fetch") {
return "ネットワークエラーが発生しました。接続を確認して、もう一度お試しください。";
}
if (err.name === "ValidationError") {
return err.message; // 事前にユーザー向け文言で投げている想定
}
return "エラーが発生しました。時間をおいて再度お試しください。";
}
JavaScriptこの関数は、
「生の err を受け取って、ユーザーに見せられる一文に変換する」役割を持ちます。
3. 画面側では「変換済みメッセージ」だけを使う
先ほどの loadUsers に組み込むと、こうなります。
async function loadUsers() {
try {
const res = await fetch("/api/users");
if (!res.ok) {
throw new Error("HTTP error " + res.status);
}
const data = await res.json();
renderUsers(data);
} catch (err) {
console.error(err); // 開発者向け
const message = toUserFriendlyMessage(err); // ユーザー向け
showErrorMessage(message);
}
}
JavaScriptここで重要なのは、
画面側は「ユーザー向けメッセージを作るロジック」を持たず、toUserFriendlyMessage に任せていることです。
これにより、
エラー表示のルールを変えたいときはtoUserFriendlyMessage だけ直せばよくなります。
API エラーをユーザー向けに整形する具体例
サーバーからのエラー JSON をそのまま見せない
API が次のような JSON を返してくるとします。
{
"error": {
"code": "EMAIL_ALREADY_USED",
"message": "This email is already registered.",
"details": {
"email": "Email already exists"
}
}
}
これをそのままユーザーに見せると、
英語だったり、内部的なコードだったりして不親切です。
そこで、まずはこの JSON を「アプリ内のエラーオブジェクト」に変換します。
前に出てきた 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 に変換する
async function callApi(url, options = {}) {
const response = await fetch(url, options);
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ここまでで、
「HTTP エラー」や「サーバー側のエラー JSON」が、ApiError という一つの形に揃いました。
ApiError をユーザー向けメッセージに変換する
次に、ApiError をユーザー向けに翻訳します。
function toUserFriendlyMessage(err) {
if (err instanceof ApiError) {
if (err.code === "EMAIL_ALREADY_USED") {
return "このメールアドレスは既に登録されています。別のメールアドレスをお試しください。";
}
if (err.status >= 500) {
return "サーバーでエラーが発生しました。時間をおいて再度お試しください。";
}
return err.message;
}
if (err.name === "TypeError" && err.message === "Failed to fetch") {
return "ネットワークエラーが発生しました。接続を確認して、もう一度お試しください。";
}
return "エラーが発生しました。時間をおいて再度お試しください。";
}
JavaScriptこれで、
同じ ApiError でも code や status によって、
ユーザー向けメッセージを変えられるようになりました。
ここが重要です。
ユーザー向けエラー表示は、
「サーバーからの生メッセージをそのまま出す」のではなく、
「アプリとしてどう伝えたいか」をコードで決める行為 です。
そのために、カスタムエラーと変換関数を組み合わせます。
UI としての「見せ方」も設計の一部
alert だけで終わらせない
学習の最初は alert(message) で十分ですが、
実際のアプリでは、もう少し丁寧な UI にしたくなります。
例えば、画面上部にエラーバナーを出す関数を用意します。
function showErrorMessage(message) {
const box = document.getElementById("error-box");
box.textContent = message;
box.style.display = "block";
}
JavaScriptこれを使えば、
非同期処理のどこでエラーが起きても、
同じ場所に、同じスタイルでエラーを表示できます。
catch (err) {
const message = toUserFriendlyMessage(err);
showErrorMessage(message);
}
JavaScriptこうすると、
「このアプリは、エラーが起きたらここに赤いバーが出る」
という一貫した体験を作れます。
入力エラーはフィールドの近くに出す
ユーザー向けエラー表示で特に大事なのが、
「入力エラー(バリデーションエラー)」です。
これは、画面上部のバナーだけでなく、
該当フィールドの近くにメッセージを出す方が親切です。
例えば、サーバーから次のようなエラーが返ってきたとします。
{
"error": {
"code": "VALIDATION_ERROR",
"details": {
"email": "メールアドレスの形式が正しくありません",
"password": "パスワードは8文字以上にしてください"
}
}
}
これを FormValidationError のようなカスタムエラーに変換し、fieldErrors を持たせておけば、
フィールドごとにメッセージを表示できます。
class FormValidationError extends Error {
constructor(message, fieldErrors) {
super(message);
this.name = "FormValidationError";
this.fieldErrors = fieldErrors;
}
}
JavaScript呼び出し側では、こう扱えます。
catch (err) {
if (err instanceof FormValidationError) {
showFieldErrors(err.fieldErrors); // email, password などに表示
} else {
const message = toUserFriendlyMessage(err);
showErrorMessage(message);
}
}
JavaScriptここが重要です。
ユーザー向けエラー表示は、
「どこに」「どの粒度で」見せるかも含めて設計するもの です。
フォームならフィールドの近く、
致命的なエラーなら画面全体のバナー、
というように、場所も一貫させていきます。
「責めない」「行動を示す」メッセージを書く
ユーザーを責める文言は避ける
エラー文言でやりがちなのが、
「あなたが間違えました」と聞こえてしまう書き方です。
例えば、
「メールアドレスが不正です」よりも、
「有効なメールアドレスを入力してください」の方が柔らかく、
次に何をすればいいかも分かりやすいです。
同じように、
「サーバーエラー」よりも、
「サーバーでエラーが発生しました。時間をおいて再度お試しください。」
と書くことで、「待てばいいのか」が伝わります。
「次の一手」を必ず含める
ユーザー向けエラー表示には、
可能な限り「次の一手」を含めるようにします。
ネットワークエラーなら「接続を確認して、もう一度お試しください」。
サーバーエラーなら「時間をおいて再度お試しください」。
入力エラーなら「〇〇を入力してください」「△△は8文字以上にしてください」。
コード上でも、toUserFriendlyMessage を書くときに、
「このメッセージを読んだユーザーは、何をすればいいか分かるか?」
を自分に問いかけてみてください。
ここが重要です。
ユーザー向けエラー表示のゴールは、
「エラーの説明」ではなく、「ユーザーの迷子を防ぐこと」 です。
そのために、メッセージの中に「行動」を必ず埋め込む意識を持ってください。
初心者として「ユーザー向けエラー表示」で本当に押さえてほしいこと
生の Error や API レスポンスは、まず「開発者向けの素材」だと考える。
ユーザーに見せる前に、必ず「翻訳レイヤー」(変換関数やカスタムエラー)を通す。
非同期処理の catch では、
「ログ用(生の err)」と「ユーザー向けメッセージ」を分けて扱う。
ログには詳細を、ユーザーには短く分かりやすい文言を。
カスタムエラーや整形関数を使って、
ネットワークエラー、API エラー、バリデーションエラーなどを分類し、
種類ごとにユーザー向けメッセージを決める。
UI としては、
致命的なエラーは画面上部のバナー、
入力エラーはフィールドの近く、
というように「どこに出すか」も一貫させる。
そして何より、
メッセージには必ず「次に何をすればいいか」を含める。
ユーザーを責めず、迷子にしない。
コードを書くとき、
catch の中で一度立ち止まって、
「このエラーを見たユーザーは、何を感じて、何をすればいいか分かるだろうか?」
と自分に問いかけてみてください。
その問いに答える形で、toUserFriendlyMessage や showErrorMessage を少しずつ育てていくと、
あなたのアプリの“エラー体験”は、確実に一段レベルアップします。
