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

APP JavaScript
スポンサーリンク

3日目のゴールと今日やること

3日目のテーマは
「祝日アプリを“使っていて気持ちいいツール”に近づける」 ことです。

技術の柱は変わりません。

  • fetch
  • Promise / async-await
  • エラーハンドリング

ただ、今日はそれを

  • 「今年」ボタン
  • 「前の年」「次の年」ボタン
  • 国プリセットを意識した UI
  • それらにきれいに対応する fetch とエラーハンドリング

に結びつけていきます。

新しい文法はほぼ出てきません。
2日目までに作った「型」を、どう応用するかがテーマです。


2日目までの祝日アプリをざっくり整理する

あなたのアプリは、すでにこんな流れを持っています。

  • 年を入力
  • 国コードを選択
  • 「祝日を取得」ボタンを押す
  • fetch で Nager.Date API にアクセス
  • async/await で結果を待つ
  • try/catch でエラーを受け止める
  • HTTP ステータスや配列かどうかをチェック
  • ローディング表示を出す・戻す

今日はここに「年を動かす操作」を足していきます。


今年ボタン・前後の年ボタンを考える

UI のイメージ

HTML はこんな感じを想像してください。

<input id="yearInput" type="number" placeholder="年を入力(例: 2025)" />

<button id="thisYearButton">今年</button>
<button id="prevYearButton">前年</button>
<button id="nextYearButton">翌年</button>

<select id="countrySelect">
  <option value="JP">日本 (JP)</option>
  <option value="US">アメリカ (US)</option>
  <option value="DE">ドイツ (DE)</option>
</select>

<button id="fetchButton">祝日を取得</button>

<div id="status"></div>
<div id="result"></div>

JavaScript 側では、これらを取得します。

const yearInput = document.getElementById("yearInput");
const countrySelect = document.getElementById("countrySelect");
const fetchButton = document.getElementById("fetchButton");
const thisYearButton = document.getElementById("thisYearButton");
const prevYearButton = document.getElementById("prevYearButton");
const nextYearButton = document.getElementById("nextYearButton");
const statusDiv = document.getElementById("status");
const resultDiv = document.getElementById("result");
JavaScript

「今年」をセットする関数を作る

Date オブジェクトから今年を取る

JavaScript の Date を使えば、
現在の年を簡単に取れます。

function getCurrentYear() {
  return new Date().getFullYear();
}
JavaScript

これを使って、「今年」ボタンの動きを決めます。

function setThisYear() {
  const year = getCurrentYear();
  yearInput.value = year;
  statusDiv.textContent = `${year}年を選択しました。`;
}
JavaScript

イベント登録はこうです。

thisYearButton.addEventListener("click", setThisYear);
JavaScript

ここではまだ fetch は呼びません。
「年をセットする」と「祝日を取得する」を分けておくと、
コードが整理しやすくなります。


前の年・次の年ボタンのロジック

年を増減させる小さな関数

まず、「今入力欄に入っている年」を安全に数値に変換する関数を作ります。

function getYearFromInput() {
  const raw = yearInput.value.trim();
  const year = Number(raw);

  if (!Number.isInteger(year)) {
    return null;
  }
  return year;
}
JavaScript

これを使って、前年・翌年をこう書きます。

function setPrevYear() {
  const current = getYearFromInput() ?? getCurrentYear();
  const nextYear = current - 1;
  yearInput.value = nextYear;
  statusDiv.textContent = `${nextYear}年を選択しました。`;
}

function setNextYear() {
  const current = getYearFromInput() ?? getCurrentYear();
  const nextYear = current + 1;
  yearInput.value = nextYear;
  statusDiv.textContent = `${nextYear}年を選択しました。`;
}
JavaScript

イベント登録はこうです。

prevYearButton.addEventListener("click", setPrevYear);
nextYearButton.addEventListener("click", setNextYear);
JavaScript

ここでも、まだ fetch は呼びません。
「年を動かす」と「API を叩く」を分離しておくのがポイントです。


年のバリデーションを再利用できる形にする

2日目の parseYear を活かす

2日目で作った parseYear を、3日目でもそのまま使います。

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

ここまでで、

  • 年をセットする(今年・前年・翌年)
  • 年をチェックする(parseYear)

という「入力まわりの責任」がきれいに分かれました。


ローディング表示を状態として扱う

isLoading を持つ

2日目で作った setLoading を、3日目でも使います。

const state = {
  isLoading: false
};

function setLoading(isLoading, message) {
  state.isLoading = isLoading;

  if (isLoading) {
    statusDiv.textContent = message || "取得中です…";
    fetchButton.disabled = true;
    thisYearButton.disabled = true;
    prevYearButton.disabled = true;
    nextYearButton.disabled = true;
  } else {
    fetchButton.disabled = false;
    thisYearButton.disabled = false;
    prevYearButton.disabled = false;
    nextYearButton.disabled = false;
  }
}
JavaScript

ポイントは、
「ローディング中は年を動かすボタンも無効にする」ことです。

API 通信中に年を変えられると、
ユーザーが「どの年の結果かわからない」と感じることがあります。


fetch / async-await / エラーハンドリングの“型”を固める

3日目版 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;

  setLoading(true, `${year}年の祝日を取得中です…`);
  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

ここに、fetch / async-await / エラーハンドリングの「型」が全部入っています。


重要ポイント1:try の中は「成功したときのストーリー」にする

try ブロックの中は、
できるだけ「うまくいったときの流れ」だけを書くようにします。

  • URL を組み立てる
  • fetch でレスポンスを取る
  • HTTP ステータスをチェックする
  • JSON に変換する
  • 配列かどうかチェックする
  • 件数 0 なら「該当なし」
  • それ以外なら表示する

失敗したときのことは、
HTTP エラーの if ブロックと catch に任せます。

こうすると、
コードが「物語」として読めるようになります。


重要ポイント2:エラーの“責任の場所”を意識する

この関数の中には、いろんなエラーがあります。

  • 入力が不正 → parseYear の責任
  • HTTP エラー → response.ok のチェック
  • データ形式がおかしい → Array.isArray のチェック
  • 該当なし → length === 0 のチェック
  • ネットワークエラー → catch

それぞれの責任が、
どこで扱われているかがはっきりしていると、
あとから読むときにとても楽です。


祝日一覧の表示を少しだけ改善する

年と国コードをタイトルに出す

2日目の renderHolidays を、3日目でも使います。

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

これで、

  • 「今どの年・どの国を見ているか」
  • 「ボタンで年を動かした結果がどう反映されているか」

が一目でわかるようになります。


ボタンと fetch をどうつなぐか

「年を変える」と「取得する」を分ける設計

今の設計では、

  • 今年ボタン → 年だけ変える
  • 前年・翌年ボタン → 年だけ変える
  • 祝日を取得ボタン → 年と国を使って API を叩く

という分担になっています。

もし「年を変えたら自動で取得したい」と思ったら、
こう書くこともできます。

function setPrevYearAndFetch() {
  setPrevYear();
  fetchHolidays();
}

prevYearButton.addEventListener("click", setPrevYearAndFetch);
JavaScript

でも、3日目の段階では
「年を変える」と「取得する」を分けておいた方が、
コードの責任がはっきりしていて学びやすいです。


3日目のまとめ

今日あなたがやったことを、言葉で整理してみます。

  • 今年ボタンで、現在の年を入力欄にセットするようにした
  • 前年・翌年ボタンで、入力欄の年を増減できるようにした
  • 年の取得とバリデーションを関数に分けて整理した
  • ローディング状態を state として持ち、年を動かすボタンも含めて一括で無効化するようにした
  • fetch / async-await / try-catch の「型」を、祝日アプリの中で固めた
  • エラーの責任(入力・HTTP・データ形式・該当なし・ネットワーク)を、コード上で分けて扱った
  • 表示に年と国コードを含めて、UI と内部状態のつながりをわかりやすくした

どれも新しい文法ではなく、
「同じ型をどう応用するか」「責任をどう分けるか」 の話です。


今日いちばん深く理解してほしいこと

3日目の本質は、

「fetch / async-await / エラーハンドリングは、“アプリのストーリー”の中に溶け込ませるもの」

ということです。

年を動かす
国を選ぶ
祝日を取得する
結果を表示する
失敗したら理由を伝える

この一連の流れの中に、
fetch と Promise とエラーハンドリングが自然に組み込まれている。

ここまで来ているあなたは、
もう「API を叩くコードを書く人」ではなく、
「API を使ったアプリを設計する人」 の側に足を踏み入れています。

4日目では、この祝日アプリに
「お気に入りの祝日」「ローカルストレージ保存」「状態管理の整理」
などを足して、さらに“毎日使えるツール”に育てていきます。

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