失敗を画面に出す
このレッスンで分かること
- console.log に出した失敗は、利用者から見れば起きていないのと同じだということ
- エラーの表示は class の付け外しで切り替え、CSS 側に見た目を任せられること
- 次の読み込みを始める前に、前の失敗の跡を消す必要があること
失敗を画面に出す とは
catch で受け取った失敗を、コンソールではなく画面の決まった場所に出し、次の操作で消えるところまで作ることです。
ここまでで足りていないこと
前の 2 回で、失敗を受け止める書き方と、失敗の種類を自分で作る書き方をやった。どちらも catch までは書けている。足りていないのは、その先である。
try {
render(works);
} catch (error) {
console.log(error.message);
}これを書いた人は「エラー処理を書いた」と思っている。だが利用者は開発者ツールを開かない。画面には何も出ず、押したボタンは反応せず、作品一覧は空のままである。利用者から見れば、失敗したのではなく壊れている。
失敗の表示は 3 つの問いに答えられていればよい。何が起きたのか、自分のせいなのか、次に何をすればよいのか。この 3 つが 1 文で伝わるなら、それ以上の情報はコンソールに置いてよい。
この回では、作品データの読み込みが失敗したときに、その理由を画面へ出す。次の章から扱う fetch の失敗も、出す場所はここと同じである。
表示の切り替えは class で行う
エラーの表示を出したり消したりするとき、JavaScript から style.display を直接書き換えたくなる。やめたほうがよい。見た目の指定が CSS と JavaScript に分かれて、あとから配色を変える人がどちらを直せばよいか分からなくなる。
CSS 側に状態ごとの見た目を書いておき、JavaScript は class を付け外しするだけにする。入門の第8章で覚えた classList がそのまま使える。
.error-panel {
display: none;
}
.error-panel.is-error {
display: block;
}errorPanel.textContent = "作品を読み込めませんでした";
errorPanel.classList.add("is-error");この形なら、あとで赤い枠を付けたくなっても、揺らす動きを足したくなっても、直すのは CSS だけで済む。
用意してある HTML はこうなっている。role="alert" を付けてあるのは、画面読み上げに使う道具にも「今この文が出た」と伝わるようにするためである。
<p class="error-panel" id="error-panel" role="alert"></p>
<div class="work-list" id="work-list"></div>出すだけでなく、消す
作りはじめでいちばん抜けやすいのが、消すほうである。壊れたデータを読んで赤い枠が出たあと、正しいデータを読み直すと作品は並ぶ。しかし赤い枠を消し忘れると、成功しているのに失敗の文言が残ったままになる。
だから読み込みを始めるところで、まず前回の跡を消す。
| する場所 | すること |
|---|---|
| 読み込みの開始時 | 前の失敗の表示を消す |
| 成功したとき | 一覧を描き直す |
| 失敗したとき | 理由を出し、一覧を空にする |
「開始時に消す」を 1 か所にまとめておくと、これから読み込みの入口が増えても書き足す必要がない。第9章で読み込み中の表示を足すときも、同じ場所に並べる。
出す文言を決める
error.message をそのまま画面に出すと、利用者に「Unexpected end of JSON input」と見せることになる。伝わらないうえ、環境によって文面が変わる。
出すのは自分で決めた 1 文にして、原因の手がかりだけを添える。今回は error.name を括弧に入れて添える形にする。問い合わせを受けたときに、どの種類の失敗だったかが分かる。
showError("作品を読み込めませんでした (" + error.name + ")");失敗の種類ごとに文言を変えたいときは、前のレッスンで作った自前のエラークラスが効いてくる。error instanceof ValidationError なら入力のやり直しを促し、そうでなければ時間をおいて試すよう伝える、という分け方ができる。
| 失敗の種類 | 画面に出す内容 |
|---|---|
| 入力の不備 | どの項目をどう直せばよいか |
| データの解析に失敗 | 読み込めなかったことと、もう一度試せること |
| 想定外 | 想定外である旨。細かい内容はコンソールへ |
一部が壊れても、全部を止めない
読み込み全体を 1 つの try で囲むと、5 件のうち 1 件だけ形がおかしいときにも一覧がまるごと消える。1 件ずつ描く場所を try で囲めば、壊れた 1 件だけを飛ばして残りを出せる。
parsed.forEach(function (work) {
try {
workList.append(createWorkCard(work));
} catch (error) {
skipped = skipped + 1;
}
});どちらがよいかは、出せない部分がどれくらい大事かで決まる。作品一覧のように「1 件欠けても意味が通じる」ものは 1 件ずつ囲んでよい。合計金額のように「1 件欠けたら数字が嘘になる」ものは、全体で止めて失敗と伝えるほうが正しい。
部分的に飛ばしたときは、飛ばしたことを黙っていない。何件を出せなかったのか、小さくてよいので画面に残す。
今回の課題は前者ではなく、読み込みそのものが失敗する形で作る。分かれ道があることだけ覚えておいてほしい。
課題ですること
loadWorksText は文字列を返すだけの関数で、"broken" を渡すと途中で切れた JSON を返す。書き換えないこと。書くのは次の 3 本である。
showError(message)で、文を入れてis-errorを付け、一覧を空にしてis-emptyを付けるclearError()で、文を消してis-errorとis-emptyを外すshowWorks(kind)で、開始時にclearErrorを呼び、try の中で解析して並べ、catch でshowErrorを呼ぶ
配列でない中身が返ってきたときも失敗として扱う。Array.isArray で調べて、違えば自分で throw する。JSON としては読めてしまうので、JSON.parse は例外を投げてくれない。ここは自分で見つけて投げる場所である。
判定は 5 つある。押して並ぶこと、壊れたデータで文言が出ること、そのとき一覧が空になること、読み直すと表示が消えること、そして最初は何も出ていないことである。1 つずつ通していけばよい。
なお、このプレビューでは localStorage が使えない。失敗した回数を覚えておきたくなっても、保存の口は呼ばずに変数で持つこと。
よくある間違い
成功したときに前の表示を消していない
function showWorks(kind) {
try {
const parsed = JSON.parse(loadWorksText(kind));
workList.textContent = "";
parsed.forEach(function (work) {
workList.append(createWorkCard(work));
});
} catch (error) {
showError("作品を読み込めませんでした");
}
}これは成功したときに is-error を外していない。一度失敗すると、そのあと何回成功しても赤い枠が残る。try に入る前に clearError() を呼ぶ。
エラーの文を alert で出す
catch (error) {
alert("読み込みに失敗しました");
}alert は押すまで消えず、押したら跡が残らない。利用者は文言を読み返せず、画面のどこが失敗したのかも分からない。失敗した場所の近くに、消えない形で出す。
表示を隠すのに要素を消してしまう
errorPanel.remove();これで見えなくはなるが、次に失敗したときに出す先が無くなる。隠すのと消すのは違う。付け外しするのは class だけにする。
課題
- showError(message) で #error-panel に文を入れ、is-error を付ける。一覧は空にして #work-list に is-empty を付ける
- clearError() で #error-panel の文を消し、is-error と is-empty を外す
- showWorks(kind) は最初に clearError() を呼び、try の中で loadWorksText(kind) の文字列を JSON.parse する
- 解析した結果が配列でなければ自分で throw し、catch で showError を呼ぶ
- 画面に出す文言は「作品を読み込めませんでした」で始め、括弧の中に error.name を添える