JavaScript | 1 日 120 分 × 7 日アプリ学習:API通信アプリ(Nager.Date API)

APP JavaScript
スポンサーリンク

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日目では、この祝日アプリに
「国のプリセット」「今年ボタン」「前後の年へ移動」
などを足して、
さらに“使っていて気持ちいいツール”に育てていきます。

タイトルとURLをコピーしました