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

APP JavaScript
スポンサーリンク

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

7日目のテーマは
「Nager.Date API 祝日アプリを“中級編の完成形”としてまとめる」ことです。

ここまで 6 日間で、あなたは
年と国を指定して祝日を取得する機能
ローディング表示
エラーハンドリング
前年・翌年・今年ボタン
お気に入り機能と localStorage 保存
複数国比較(Promise.all)
状態管理(state)
fetch の共通化
まで作り上げてきました。

7日目は、それらをひとつの「完成したアプリ」として整理しながら、
fetch、Promise / async-await、エラーハンドリングの型を、自分の中に定着させる日です。

ここからは「書ける」から「設計できる」へ、意識を一段上げていきます。


アプリ全体の構造を言葉で整理する

まず、あなたの Nager.Date 祝日アプリが、どんな層でできているかを整理します。

一番下の層にあるのが「API 通信」です。
ここには requestJson と、Nager.Date 専用の fetchPublicHolidays があります。

その上に「状態管理(state)」があります。
isLoading、holidays、favorites、comparison など、アプリの“今”を表す情報がここに集まっています。

さらにその上に「UI 更新」があります。
renderHolidays、renderFavorites、renderComparison などが、state の中身を画面に反映します。

一番上に「イベント処理」があります。
ボタンのクリックや入力の変化に応じて、fetchHolidays や compareCountries などが呼ばれます。

この四層構造を意識すると、
どこで何をしているかが一気に見通しやすくなります。


fetch / async-await / エラーハンドリングの“完成形”をもう一度確認する

まずは、共通の requestJson から振り返ります。

async function requestJson(url) {
  try {
    const response = await fetch(url);

    if (!response.ok) {
      if (response.status === 404) {
        throw new Error("データが見つかりませんでした。");
      }
      if (response.status >= 500) {
        throw new Error("サーバー側でエラーが発生しています。時間をおいて再試行してください。");
      }
      throw new Error(`HTTPエラー(${response.status})`);
    }

    const data = await response.json();
    return data;

  } catch (error) {
    throw new Error(error.message);
  }
}
JavaScript

この関数は、fetch の「生の挙動」をアプリから隠してくれます。
HTTP ステータスの判定、JSON への変換、ネットワークエラーの扱いなどを、すべてここに閉じ込めています。

上の層から見ると
「URL を渡したら JSON が返ってくる。失敗したら Error が投げられる」
という、とてもシンプルな約束だけを意識すればよくなります。

次に、Nager.Date 専用の関数です。

async function fetchPublicHolidays(year, countryCode) {
  const url = `https://date.nager.at/api/v3/PublicHolidays/${year}/${countryCode}`;
  const data = await requestJson(url);

  if (!Array.isArray(data)) {
    throw new Error("予期しない形式のデータが返されました。");
  }

  if (data.length === 0) {
    throw new Error("祝日が見つかりませんでした。");
  }

  return data;
}
JavaScript

ここは「Nager.Date API の仕様を知っている場所」です。
配列であること、件数が 0 のときの扱いなどをここで決めておくことで、
UI 側は「祝日の配列が来る」という前提だけで動けるようになります。


単一国の祝日取得を“物語として読めるコード”にする

ここで、7日目版の 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 holidays = await fetchPublicHolidays(year, countryCode);

    updateState({ holidays });
    statusDiv.textContent = "祝日の取得に成功しました。";
    renderHolidays(holidays);

  } catch (error) {
    statusDiv.textContent = `取得中にエラーが発生しました:${error.message}`;
    console.error(error);

  } finally {
    setLoading(false);
  }
}
JavaScript

この関数を、上から順に「日本語で」読んでみてください。

年と国を入力欄から取り出す。
年が正しいかチェックする。ダメならメッセージを出して終わる。
ローディングを開始し、結果表示を一旦クリアする。
Nager.Date から祝日一覧を取得する。
成功したら state に保存し、メッセージを出し、画面に描画する。
失敗したらエラーメッセージを出す。
最後にローディングを終了する。

これが「ストーリーとして読めるコード」です。
fetch や async/await の細かい話は、すべて下の層に隠れています。


複数国比較を Promise.all で“きれいに”扱う

次に、複数国比較の関数をもう一度整理します。

async function fetchMultipleCountries(year, countries) {
  const promises = countries.map((code) =>
    fetchPublicHolidays(year, code)
  );

  const results = await Promise.all(promises);
  return results;
}
JavaScript

ここでやっていることは、とてもシンプルです。
国コードの配列から、fetchPublicHolidays を並べた Promise の配列を作る。
Promise.all で全部終わるまで待つ。
結果として「国ごとの祝日配列の配列」を返す。

これを UI 側から使うと、こうなります。

async function compareCountries() {
  const rawYear = yearInput.value.trim();
  const parsed = parseYear(rawYear);

  if (!parsed.ok) {
    statusDiv.textContent = parsed.message;
    return;
  }

  const year = parsed.value;
  const countries = ["JP", "US", "DE"];

  setLoading(true, `${year}年の複数国の祝日を取得中です…`);
  resultDiv.textContent = "";

  try {
    const results = await fetchMultipleCountries(year, countries);

    updateState({ comparison: results });
    renderComparison(results, countries, year);
    statusDiv.textContent = "複数国の祝日取得に成功しました。";

  } catch (error) {
    statusDiv.textContent = `比較中にエラーが発生しました:${error.message}`;
    console.error(error);

  } finally {
    setLoading(false);
  }
}
JavaScript

ここでも、上から順に読むとストーリーになっています。

年をチェックする。
比較したい国のリストを決める。
ローディングを開始する。
複数国の祝日を同時に取得する。
成功したら state に保存し、比較表示を行う。
失敗したらエラーメッセージを出す。
最後にローディングを終了する。

Promise.all の存在を意識しなくても、
「複数国の祝日をまとめて取ってくる関数」として読めるのが理想です。


ローディング表示と state を“アプリ全体のルール”にする

ローディング表示は、アプリ全体で一貫しているほど使いやすくなります。

function setLoading(isLoading, message) {
  updateState({ isLoading });

  if (isLoading) {
    statusDiv.textContent = message || "処理中です…";
  }

  fetchButton.disabled = isLoading;
  compareButton.disabled = isLoading;
  thisYearButton.disabled = isLoading;
  prevYearButton.disabled = isLoading;
  nextYearButton.disabled = isLoading;
}
JavaScript

ここで大事なのは、
「ローディング中は、ユーザーが混乱しそうな操作を全部止める」
というルールをコードにしていることです。

単一国の取得でも、複数国比較でも、
setLoading(true, …) と setLoading(false) を呼ぶだけで、
同じ体験が提供されます。

これが「状態管理を中心にした設計」です。


エラーハンドリングを“責任の場所”で分けて考える

7日目では、エラーハンドリングをもう一段整理しておきます。

入力エラーは parseYear の責任です。
ここでは「年が数字か」「範囲内か」をチェックし、
ダメなら ok: false とメッセージを返します。

HTTP エラーとネットワークエラーは requestJson の責任です。
ここではステータスコードを見て、
404、500 番台、それ以外を分けて Error を投げます。

データ形式エラーと「該当なし」は fetchPublicHolidays の責任です。
配列でなければ「予期しない形式」、
配列だが 0 件なら「祝日が見つかりませんでした」として Error を投げます。

UI 側の関数(fetchHolidays や compareCountries)は、
try の中で「うまくいったときのストーリー」を書き、
catch で error.message をユーザー向けに表示します。

この分担ができていると、
「どの種類のエラーがどこで処理されているか」が明確になります。
結果として、コードが直しやすく、拡張しやすくなります。


7日目のまとめと、あなたがもう持っている力

ここまでで、Nager.Date 祝日アプリはこういう姿になりました。

単一国の祝日を取得できる。
今年・前年・翌年を簡単に切り替えられる。
ローディング表示が一貫して動く。
入力エラー、HTTP エラー、データ形式エラー、該当なし、ネットワークエラーを、原因別に扱えている。
お気に入りの祝日を保存し、localStorage に永続化できる。
お気に入りから再検索できる。
複数国の祝日を Promise.all で同時に取得し、比較表示できる。
state を中心に、fetch、UI、イベントが整理されている。
fetch は requestJson に共通化され、Nager.Date 専用の fetchPublicHolidays によって API 仕様が一箇所に集約されている。

これらはすべて
fetch
Promise / async-await
エラーハンドリング
という 3 つの柱の上に成り立っています。

今日いちばん伝えたいのは、
「ここまで来たあなたは、もう“どんな API でも同じ型で扱える”ところまで来ている」
ということです。

URL が違うだけ。
返ってくる JSON の形が違うだけ。
やっていることの本質は、Nager.Date でも、翻訳 API でも、天気 API でも同じです。

fetch で取りに行く。
async/await で待つ。
try/catch で失敗を受け止める。
state に反映する。
UI に描画する。

この流れを、自分の言葉で説明できるようになっていたら、
もう「API が怖い初心者」ではありません。

ここから先は、
あなたが作りたいアプリに合わせて、
この型の上に機能を積み上げていくだけです。

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