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

APP JavaScript
スポンサーリンク

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

6日目のテーマは
「ExchangeRate.host 通貨変換アプリを“読みやすく・直しやすく・拡張しやすい設計”に進化させる」
ことです。

ここまでであなたは、

  • 金額変換
  • 入力チェック
  • ローディング表示
  • エラーハンドリング
  • 逆変換ボタン
  • プリセット通貨ペア
  • お気に入り保存(state + localStorage)
  • 複数通貨変換(Promise.all)

といった、通貨変換アプリの本質をすでに体験しています。

6日目は、これらを 「設計として整理する」 回です。

つまり、
fetch / async‑await / エラーハンドリングを“アプリ全体の型”として扱えるようにする
ことが目的です。


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

あなたのアプリは、次の 4 層で構成されています。

API 通信層(最下層)

  • requestJson
  • convertViaAPI
  • convertToMultiple

ここは「API の仕様を知っている場所」です。

状態管理層(state)

  • isLoading
  • favorites
  • lastResult

アプリの“今”を表す情報がここに集まります。

UI 更新層

  • renderConversion
  • renderMultiple
  • renderFavorites

state の内容を画面に反映する層です。

イベント処理層(最上層)

  • convertCurrency
  • convertToPopularCurrencies
  • swapCurrencies
  • プリセットボタンのクリック処理

ユーザー操作に応じて、上の層を呼び出します。

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


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

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

API 通信の共通関数

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

    if (!response.ok) {
      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 パースエラー

をすべて Error として上に投げる 役割を持っています。

UI 側は「成功したか・失敗したか」だけを扱えばよくなり、
コードが読みやすくなります。


ExchangeRate.host 専用の変換関数を整理する

convertViaAPI(API の仕様を知っている場所)

async function convertViaAPI(from, to, amount) {
  const url =
    `https://api.exchangerate.host/convert` +
    `?from=${encodeURIComponent(from)}` +
    `&to=${encodeURIComponent(to)}` +
    `&amount=${encodeURIComponent(amount)}`;

  const data = await requestJson(url);

  if (!data || data.success === false) {
    throw new Error("レートの取得に失敗しました。");
  }

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

  return {
    converted: data.result,
    rate: data.info?.rate
  };
}
JavaScript

深掘りポイント

convertViaAPI は
「ExchangeRate.host の仕様を知っている唯一の関数」
です。

UI 側は API の細かい仕様を知らなくてよくなり、
コードが読みやすくなります。


UI 側の convertCurrency を“ストーリーとして読める”形にする

6日目版 convertCurrency(完成形)

async function convertCurrency() {
  const rawAmount = amountInput.value.trim();
  const from = fromSelect.value;
  const to = toSelect.value;

  if (from === to) {
    statusDiv.textContent = "異なる通貨を選択してください。";
    resultDiv.textContent = "";
    return;
  }

  const parsed = parseAmount(rawAmount);
  if (!parsed.ok) {
    statusDiv.textContent = parsed.message;
    resultDiv.textContent = "";
    return;
  }

  const amount = parsed.value;

  setLoading(true, `${amount} ${from}${to} に変換中です…`);
  resultDiv.textContent = "";

  try {
    const { converted, rate } = await convertViaAPI(from, to, amount);

    updateState({ lastResult: { from, to, amount, converted, rate } });
    statusDiv.textContent = "通貨変換に成功しました。";
    renderConversion(from, to, amount, converted, rate);

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

  } finally {
    setLoading(false);
  }
}
JavaScript

深掘りポイント

この関数は、
「変換の流れをそのまま読める」
という設計になっています。

  • 入力チェック
  • ローディング開始
  • convertViaAPI で変換
  • 成功時の表示
  • 失敗時のメッセージ
  • ローディング終了

fetch の細かい処理は
convertViaAPI と requestJson に隠れているため、
とても読みやすくなっています。


複数通貨変換を Promise.all で整理する

convertToMultiple(複数通貨の変換)

async function convertToMultiple(from, amount, targets) {
  const promises = targets.map((to) =>
    convertViaAPI(from, to, amount)
  );

  const results = await Promise.all(promises);

  return results.map((res, index) => ({
    from,
    to: targets[index],
    amount,
    converted: res.converted,
    rate: res.rate
  }));
}
JavaScript

深掘りポイント

Promise.all を使うと、

  • 並列で API を叩ける
  • 全部終わるまで待てる
  • 結果をまとめて扱える

というメリットがあります。


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

setLoading を state と連動させる

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

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

  convertButton.disabled = isLoading;
  swapButton.disabled = isLoading;

  const presetButtons = document.querySelectorAll(".preset");
  presetButtons.forEach((btn) => (btn.disabled = isLoading));

  const multiButton = document.getElementById("multiButton");
  if (multiButton) multiButton.disabled = isLoading;
}
JavaScript

深掘りポイント

ローディング中は、
ユーザーが混乱しそうな操作をすべて止めることで、
アプリの一貫性が保たれます。


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

どこで何を扱うか

入力エラー → parseAmount
HTTP エラー → requestJson
API レベルの失敗 → convertViaAPI
データ形式エラー → convertViaAPI
ネットワークエラー → catch

このように、
エラーの責任を関数ごとに分ける
ことで、コードが読みやすくなります。


6日目のまとめ

今日あなたがやったことを整理すると、こうなります。

  • fetch を requestJson に共通化
  • ExchangeRate.host 専用の convertViaAPI を作成
  • UI 側の convertCurrency を「読みやすい流れ」に整理
  • Promise.all で複数通貨変換を整理
  • ローディング表示を state と連動
  • エラーハンドリングを原因別に整理
  • 状態管理(state)をアプリの中心に置いた

どれも新しい文法ではなく、
「fetch / async‑await / エラーハンドリングを“設計として扱う”」
という内容です。


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

6日目の本質は、

「コードは“動けばいい”から、“読めて直せて拡張できる”に育てていくもの」
ということです。

通貨変換アプリは、
fetch → await → JSON → 分岐 → state → UI
という“型”の上に成り立っています。

7日目では、このアプリを
中級編の完成形としてまとめる
回に入ります。

あなたのアプリが「本当に使えるツール」になる瞬間です。

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