2日目のゴールと今日やること
2日目のテーマは
「1日目で作った“祝日一覧アプリ”を、ちゃんと使えるツールに近づける」
ことです。
技術的な柱は、昨日と同じくこの3つです。
- fetch
- Promise / async-await
- エラーハンドリング(try / catch)
ただし今日は、それをもう一歩進めて、
- 入力チェックを少し丁寧にする
- ローディング表示を“状況に応じて”出せるようにする
- エラーメッセージを「原因別」に分けて、ユーザーに伝わりやすくする
というところをやっていきます。
「新しい文法を覚える」というより、
昨日書いたコードを“設計として整理する” 回です。
1日目の祝日アプリをざっくり振り返る
昨日の流れを言葉で確認する
1日目のアプリは、こんな流れでした。
- 年を入力
- 国コードを選択
- 「祝日を取得」ボタンを押す
- fetch で Nager.Date API にアクセス
- JSON をパース
- 配列かどうかチェック
- 件数 0 なら「祝日が見つかりませんでした」
- それ以外なら一覧表示
- 通信失敗なら「通信に失敗しました」
この流れ自体はとても良いです。
2日目では、ここに 「現実のユーザーが使うときのこと」 を少し足していきます。
年の入力チェックを“現実的”にする
「空かどうか」だけでは足りない
昨日はこう書きました。
if (!year) {
statusDiv.textContent = "年を入力してください。";
resultDiv.textContent = "";
return;
}
JavaScriptこれはこれで大事ですが、
現実にはこんな入力もありえます。
- 0
- 100
- 99999
- 文字列(ブラウザによっては入り得る)
Nager.Date API は、基本的に「西暦の年」を期待しています。
そこで、数値として妥当かどうか もチェックしてみます。
数値としてのチェックを追加する
function parseYear(raw) {
const year = Number(raw);
if (!Number.isInteger(year)) {
return { ok: false, message: "年は数字で入力してください。" };
}
if (year < 1900 || year > 2100) {
return { ok: false, message: "年は 1900〜2100 の範囲で入力してください。" };
}
return { ok: true, value: year };
}
JavaScriptこれを fetchHolidays の最初で使います。
async function fetchHolidays() {
const rawYear = yearInput.value.trim();
const countryCode = countrySelect.value;
const parsed = parseYear(rawYear);
if (!parsed.ok) {
statusDiv.textContent = parsed.message;
resultDiv.textContent = "";
return;
}
const year = parsed.value;
// ここから先は「year はちゃんとした数値」として扱える
}
JavaScript深掘りポイント
ここでやっていることは、
- 「入力が空かどうか」ではなく「意味のある値かどうか」を見る
- エラーメッセージを「何がダメなのか」がわかる形にする
という、アプリとしての優しさ です。
ローディング表示を“状況に応じて”出す
昨日のローディングを思い出す
1日目では、こうしていました。
function startLoading(message) {
statusDiv.textContent = message || "取得中です…";
fetchButton.disabled = true;
}
function endLoading() {
fetchButton.disabled = false;
}
JavaScriptこれでも十分ですが、
2日目では「ローディング中かどうか」を状態として持っておくと便利です。
isLoading を状態として持つ
const state = {
isLoading: false
};
function setLoading(isLoading, message) {
state.isLoading = isLoading;
if (isLoading) {
statusDiv.textContent = message || "取得中です…";
fetchButton.disabled = true;
} else {
fetchButton.disabled = false;
}
}
JavaScriptそして、fetchHolidays ではこう使います。
setLoading(true, "祝日を取得中です…");
resultDiv.textContent = "";
try {
// fetch 〜 JSON 〜 分岐
} catch (error) {
// エラー処理
} finally {
setLoading(false);
}
JavaScript深掘りポイント
state.isLoading を持っておくと、
将来「ローディングスピナーを出したい」「別のボタンも無効にしたい」
となったときに、ひとつの状態から UI を組み立てられる ようになります。
fetch 部分を“パターン”として固める
昨日の fetch 部分
1日目では、だいたいこんな形でした。
const response = await fetch(url);
if (!response.ok) {
statusDiv.textContent = `サーバーエラーが発生しました。(${response.status})`;
return;
}
const data = await response.json();
JavaScriptこれを、2日目では 「共通の型」として意識する ところまで持っていきます。
エラーハンドリングを「原因別」に分ける
ざっくり 3 つのエラー
Nager.Date のような API では、エラーをざっくりこう分けられます。
- ネットワークエラー(Wi-Fi が切れているなど)
- HTTP エラー(404, 500 など)
- データの中身が想定外(配列じゃない、空配列など)
これをコードに落とすと、こうなります。
async function fetchHolidays() {
const rawYear = yearInput.value.trim();
const countryCode = countrySelect.value;
const parsed = parseYear(rawYear);
if (!parsed.ok) {
statusDiv.textContent = parsed.message;
resultDiv.textContent = "";
return;
}
const year = parsed.value;
setLoading(true, "祝日を取得中です…");
resultDiv.textContent = "";
try {
const url = `https://date.nager.at/api/v3/PublicHolidays/${year}/${countryCode}`;
const response = await fetch(url);
if (!response.ok) {
if (response.status === 404) {
statusDiv.textContent = "指定された年または国の祝日が見つかりませんでした。";
} else if (response.status >= 500) {
statusDiv.textContent = "サーバー側でエラーが発生しています。時間をおいて再試行してください。";
} else {
statusDiv.textContent = `サーバーエラーが発生しました。(${response.status})`;
}
return;
}
const data = await response.json();
if (!Array.isArray(data)) {
statusDiv.textContent = "予期しない形式のデータが返されました。";
console.error("Unexpected data:", data);
return;
}
if (data.length === 0) {
statusDiv.textContent = "祝日が見つかりませんでした。";
resultDiv.textContent = "";
return;
}
statusDiv.textContent = "祝日の取得に成功しました。";
renderHolidays(data);
} catch (error) {
statusDiv.textContent = "通信に失敗しました。ネットワークを確認してください。";
console.error(error);
} finally {
setLoading(false);
}
}
JavaScript重要ポイント1:HTTP ステータスごとにメッセージを変える
if (!response.ok) {
if (response.status === 404) {
statusDiv.textContent = "指定された年または国の祝日が見つかりませんでした。";
} else if (response.status >= 500) {
statusDiv.textContent = "サーバー側でエラーが発生しています。時間をおいて再試行してください。";
} else {
statusDiv.textContent = `サーバーエラーが発生しました。(${response.status})`;
}
return;
}
JavaScriptここでやっているのは、
- 404 → 「その条件のデータがない」
- 500 以上 → 「サーバー側の問題」
- それ以外 → 「何かしらのサーバーエラー」
というふうに、原因別にメッセージを変えている ことです。
ユーザーにとっては、
- 「自分の入力が悪いのか」
- 「サーバーが悪いのか」
- 「よくわからないけどダメなのか」
がわかるだけで、ストレスがかなり減ります。
重要ポイント2:「該当なし」と「エラー」を混ぜない
if (data.length === 0) {
statusDiv.textContent = "祝日が見つかりませんでした。";
resultDiv.textContent = "";
return;
}
JavaScriptここは昨日と同じですが、
改めてめちゃくちゃ重要です。
「データが 0 件だった」というのは、
エラーではなく“正常な結果” です。
- 通信は成功している
- サーバーも正常に動いている
- ただ、その条件に合う祝日がなかった
というだけなので、
「エラーが起きました」ではなく、
「見つかりませんでした」と伝えるのが正解です。
重要ポイント3:catch では「ユーザー向け」と「開発者向け」を分ける
} catch (error) {
statusDiv.textContent = "通信に失敗しました。ネットワークを確認してください。";
console.error(error);
}
JavaScriptここで、
- 画面には「通信に失敗しました。ネットワークを確認してください。」
- コンソールには
errorの詳細
というふうに、
ユーザーと開発者で出す情報を分けています。
error.message をそのままユーザーに見せると、
英語の長文や技術用語が出てきて、逆に不親切になることが多いです。
祝日一覧の表示を少しだけリッチにする
日付順に並んでいる前提を活かす
Nager.Date の祝日は、基本的に日付順で返ってきます。
そのまま表示してもいいですが、
「年・国名をタイトルに出す」と、アプリらしくなります。
function renderHolidays(data) {
if (data.length === 0) {
resultDiv.textContent = "";
return;
}
const countryCode = data[0].countryCode;
const year = data[0].date.slice(0, 4);
let html = `<h3>${year}年 ${countryCode} の祝日一覧</h3>`;
data.forEach((item) => {
html += `<p>${item.date}:${item.localName}(${item.name})</p>`;
});
resultDiv.innerHTML = html;
}
JavaScript深掘りポイント
ここでやっているのは、
- データの中身(date, countryCode)を UI に活かす
- 「何の一覧なのか」が一目でわかるようにする
という、小さいけれど効く工夫 です。
ボタンイベントはシンプルに保つ
fetchButton.addEventListener("click", fetchHolidays);
JavaScriptここは昨日と同じですが、
「ボタンを押したら何が起きるか」が
1 行で読み取れる のは、とても大事です。
2日目のまとめ
今日あなたがやったことを、言葉で整理してみます。
- 年の入力を「空かどうか」だけでなく「数値として妥当か」までチェックした
- エラーメッセージを「何がダメなのか」がわかる形にした
- ローディング状態を state として持ち、UI と連動させた
- fetch のエラーハンドリングを「原因別」に分けて考えた
- 404 → データがない
- 500 以上 → サーバー側の問題
- それ以外 → 一般的なサーバーエラー
- 「該当なし」と「エラー」をきちんと分けて扱った
- catch では「ユーザー向けの日本語」と「開発者向けのログ」を分けた
- 祝日一覧の表示を、年・国名付きで少しリッチにした
どれも新しい文法ではなく、
「昨日書いたコードを、アプリとしてちゃんと設計し直した」
という内容です。
今日いちばん深く理解してほしいこと
2日目の本質は、
「同じ fetch / async / await / try / catch でも、“どう扱うか”でアプリの質が変わる」
ということです。
まったく同じ Nager.Date API を叩いていても、
- 入力チェックが丁寧か
- エラーメッセージが原因別か
- ローディング表示があるか
- 「該当なし」と「エラー」を分けているか
これだけで、
「ただ動くだけのサンプル」と
「ちゃんとしたアプリ」の差が生まれます。
3日目では、この祝日アプリに
「国のプリセット」「今年ボタン」「前後の年へ移動」
などを足して、
さらに“使っていて気持ちいいツール”に育てていきます。


