一覧を読み込めなかったときも「0件です」と出してしまうと、利用者には本当に資料がないように見えます。取得失敗と0件を分けるには、件数を調べる前に、応答を受け取れたか、HTTPの結果は成功か、本文を予定のデータとして読めたかを確かめます。
題材は架空の貸出資料一覧です。関数、配列、if文を読める人向けに、取得結果を状態へ分ける処理を作ります。Node.jsでfetchとResponseを使える実行環境を用意してください。練習では応答を手元で作るため、外部サービスへの接続やアカウントは不要です。
0件と呼べるのは、一覧を読めた後
今回のデータの約束は「名前の文字列を並べたJSON配列」です。たとえば["地図帳", "植物図鑑"]なら二件、[]なら0件です。オブジェクトや数値の配列は、この一覧の形式としては受け付けません。JSONとして正しいことだけでは、使いたいデータかは決まらないためです。
MDNのFetch APIの説明では、404のような応答でもfetchがResponseを返すことが説明されています。tryとcatchを書いただけではHTTPエラーを見分けられません。応答の状態を調べる分岐が必要です。
結果は次の四種類にします。これはこの練習で決める状態名で、HTTPのステータスコードそのものではありません。
| 状態 | この一覧での意味 |
|---|---|
| ready | 成功した応答を読み、名前が一件以上あった |
| empty | 成功した応答を読み、正しい形式の空配列だった |
| http-error | 応答は来たが、成功を示す状態ではなかった |
| unavailable | 応答の取得、JSONの読取り、形式の確認のいずれかで失敗した |
unavailableを細かな原因へ分けることもできますが、ここでは利用者に「0件」と誤案内しない範囲に絞ります。原因が分かっていない段階で「あなたの通信環境が悪い」と断定する表示にはしません。
応答の状態から順に読む関数
次をload-items.mjsとして保存し、同じフォルダでnode load-items.mjsを実行します。requestの既定値はfetchです。練習では代わりの関数を渡し、同じ読取り処理へ異なる応答を届けます。Responseのコンストラクターへ本文とstatusを渡して、その応答を手元で作ります。
async function loadItems(url, request = fetch) {
try {
const response = await request(url);
if (!response.ok) {
return { status: "http-error", code: response.status };
}
const items = await response.json();
if (!Array.isArray(items) ||
!items.every(item => typeof item === "string")) {
return { status: "unavailable" };
}
return items.length === 0
? { status: "empty", items }
: { status: "ready", items };
} catch {
return { status: "unavailable" };
}
}
function messageFor(result) {
switch (result.status) {
case "ready": return `${result.items.length}件の資料を読み込みました。`;
case "empty": return "登録されている資料はありません。";
case "http-error": return "資料を取得できませんでした。";
default: return "資料を読み込めませんでした。時間をおいて試してください。";
}
}
const cases = [
["2件", async () => new Response('["地図帳","植物図鑑"]', { status: 200 })],
["0件", async () => new Response('[]', { status: 200 })],
["404", async () => new Response('[]', { status: 404 })],
["壊れたJSON", async () => new Response('{', { status: 200 })],
["違う形式", async () => new Response('{"items":[]}', { status: 200 })],
["取得時の例外", async () => { throw new TypeError("練習用の失敗"); }]
];
for (const [name, request] of cases) {
const result = await loadItems("/practice-items.json", request);
console.log(name, result.status, messageFor(result));
}
出力の状態は順にready、empty、http-error、unavailable、unavailable、unavailableです。404の応答にも空配列を入れていますが、emptyにはなりません。JSONを読むより先に、HTTPの結果で分岐しているからです。
Response.okの説明では、200から299の範囲がtrueになります。ただしokがtrueでも、本文が必要なJSON形式とは限りません。次のjsonの読取りと、配列の形式確認が残っています。
async関数は、結果を後で受け取るPromiseを返します。awaitでその完了を待ち、取得と本文の読取りを順に進めています。jsonの前にもawaitがあるのは、本文の読取りも完了を待つ処理だからです。ここでは表示処理をtryの外側へ分け、描画の不具合まで取得失敗として受け止めないようにしています。
catchで空配列を返すと、失敗が消える
一つ目の失敗は、catchにreturn []と書き、呼び出し側で長さだけを見ることです。取得できなかった場合と、正常に0件だった場合が同じ値になります。画面からは両者を区別できず、利用者が再度試す理由も伝わりません。
修正は、完成例のように状態名を含むオブジェクトを返すことです。件数を見るのはreadyまたはemptyを受け取った後に限定します。失敗のオブジェクトにはitemsを持たせず、使えない結果をうっかり一覧として渡しにくい形にしています。
再確認では「0件」と「取得時の例外」を続けて実行します。前者だけが資料なしの文になり、後者は読込み失敗になれば、この混同は解消しています。空の一覧の文言を分ける記事は案内の設計を扱いますが、ここではその表示を選ぶための取得処理が対象です。
HTTPの成功だけで、本文を信用しない
二つ目の失敗は、response.okを確認した直後に「一覧取得成功」とし、本文の形式を見ずに件数を出すことです。応答がJSONでも{"items":[]}は今回の配列形式ではありません。lengthがない値を扱い、表示がundefinedになるなどの問題につながります。
修正では、配列かどうかと、各要素が文字列かを先に確かめます。「違う形式」の結果がunavailableとなり、資料なしの表示にならないことを再確認してください。実際のAPIがitemsという項目の中に配列を返す仕様なら、その契約に合わせて関数を変える必要があります。
Response.jsonの説明にあるように、JSONとして解析できなければ例外になり、catchへ進みます。また、204のように本文がない成功応答も、今回のJSON配列という約束では読取りに失敗します。すべての成功応答をこの関数へ使えるわけではありません。本文なしを0件とするAPIなら、その扱いを仕様で確認してから明示的に分岐します。
実際の取得へつなぐ前に、出口を照合する
この例は、ブラウザーの画面やサーバーを作るものではありません。実際の取得へつなぐ場合は、用意済みのJSONのURLに対してrequestを省略して呼びます。Node.jsでは絶対URLを使い、ブラウザーでは配信元や通信の許可条件も別途確認してください。
確認用の配列へ新しいケースを加え、次を確かめます。形式の変更とHTTPの状態変更は一度に行わず、どちらが結果を変えたか分かるようにします。
- 200と
[]の組だけがemptyになり、資料なしの文を返す。 - 同じ
[]でも500へ変えるとhttp-errorになり、0件とは表示しない。 - 200と
[1,2]の組はunavailableになり、名前として数値を受け付けない。 - 正しい二件を受け取るとreadyになり、元の二つの名前がitemsに残る。
- JSON読取りや取得で失敗しても例外が外へ漏れず、案内文を選べる。
取得した後に条件で絞る段階はfilterの記事、画面の操作へつなぐ練習はJavaScriptの無料レッスンが使えます。同時に複数の取得を走らせる場合の順序制御は別の課題です。まず一回の取得について、失敗を0件へ変えていないことを確認しましょう。
記載内容へのご質問や訂正のご連絡は、hello@junkbranding.comまでお願いします。