7日目のゴールと今日やること
7日目のテーマは
「ExchangeRate.host 通貨変換アプリを“中級編の完成形”としてまとめる」
ことです。
ここまで 6 日間で、あなたはすでにかなり本格的なアプリを作っています。
金額入力
通貨ペア選択
レート取得と変換
ローディング表示
エラーハンドリング
逆変換ボタン
プリセット通貨ペア
お気に入り通貨ペア(state + localStorage)
複数通貨一括変換(Promise.all)
fetch の共通化(requestJson)
ExchangeRate.host 専用関数(convertViaAPI)
7日目は、これらを「ひとつの完成したアプリ」として整理しながら、
fetch / async‑await / エラーハンドリングの型を、自分の中に定着させる日 です。
アプリ全体の構造をもう一度“言葉で”整理する
4つの層で考える
あなたの通貨変換アプリは、次の 4 層でできています。
API 通信層
ExchangeRate.host と話す、一番下の層です。
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);
}
}
JavaScriptasync 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ここは「ExchangeRate.host の仕様を知っている場所」です。
状態管理層(state)
アプリの“今”を表す情報をまとめて持つ層です。
const state = {
isLoading: false,
favorites: [],
lastResult: null
};
function updateState(updates) {
Object.assign(state, updates);
}
JavaScriptここに、
- ローディング中かどうか
- お気に入り通貨ペア
- 最後の変換結果
などが集まります。
UI 更新層
state の内容を画面に反映する層です。
function renderConversion(from, to, amount, converted, rate) {
const rateText =
typeof rate === "number"
? `1 ${from} = ${rate} ${to}`
: "";
const html = `
<h3>通貨変換結果</h3>
<p>${amount} ${from} = ${converted} ${to}</p>
<p>${rateText}</p>
<button id="favAddButton">★ この通貨ペアをお気に入りに追加</button>
`;
resultDiv.innerHTML = html;
const favButton = document.getElementById("favAddButton");
favButton.addEventListener("click", () => {
addFavorite(from, to);
});
}
JavaScriptfunction renderFavorites() {
if (!state.favorites.length) {
favoritesDiv.textContent = "お気に入り通貨ペアはまだありません。";
return;
}
let html = "<h3>お気に入り通貨ペア</h3>";
state.favorites.forEach((item, index) => {
html += `<p data-index="${index}" class="favorite-item">
${item.from} → ${item.to}
</p>`;
});
favoritesDiv.innerHTML = html;
const items = favoritesDiv.querySelectorAll(".favorite-item");
items.forEach((el) => {
el.addEventListener("click", () => {
const index = Number(el.dataset.index);
const fav = state.favorites[index];
fromSelect.value = fav.from;
toSelect.value = fav.to;
convertCurrency();
});
});
}
JavaScriptイベント処理層
ユーザー操作に応じて、上の層を呼び出す層です。
convertButton.addEventListener("click", convertCurrency);
swapButton.addEventListener("click", swapCurrencies);
presetButtons.forEach((btn) => {
btn.addEventListener("click", () => {
const from = btn.dataset.from;
const to = btn.dataset.to;
fromSelect.value = from;
toSelect.value = to;
statusDiv.textContent = `${from} → ${to} を選択しました。`;
});
});
JavaScriptこの 4 層構造を意識できている時点で、
あなたはもう「ただ書く人」ではなく「設計できる人」の側にいます。
fetch / async‑await / エラーハンドリングの“型”を完成形として眺める
単一通貨変換の完成形
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この関数を、上から日本語で読んでみてください。
金額と通貨ペアを入力欄から取り出す。
同じ通貨ならエラーメッセージを出して終わる。
金額が正しいかチェックする。ダメならメッセージを出して終わる。
ローディングを開始し、結果表示を一旦クリアする。
ExchangeRate.host から変換結果を取得する。
成功したら state に保存し、メッセージを出し、画面に描画する。
失敗したらエラーメッセージを出す。
最後にローディングを終了する。
これが「ストーリーとして読めるコード」です。
fetch の細かい話は、すべて下の層に隠れています。
複数通貨一括変換を“Promise.all の型”として理解する
複数通貨変換の関数
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ここでやっていることは、とてもシンプルです。
通貨コードの配列から、convertViaAPI を並べた Promise の配列を作る。
Promise.all で全部終わるまで待つ。
結果として「通貨ごとの変換結果の配列」を返す。
UI 側から使うとこうなる
async function convertToPopularCurrencies() {
const rawAmount = amountInput.value.trim();
const from = fromSelect.value;
const parsed = parseAmount(rawAmount);
if (!parsed.ok) {
statusDiv.textContent = parsed.message;
return;
}
const amount = parsed.value;
const targets = ["USD", "EUR", "GBP"];
setLoading(true, `${amount} ${from} を複数通貨に変換中です…`);
resultDiv.textContent = "";
try {
const results = await convertToMultiple(from, amount, targets);
updateState({ lastResult: results });
renderMultiple(results);
statusDiv.textContent = "複数通貨への変換に成功しました。";
} catch (error) {
statusDiv.textContent = `変換中にエラーが発生しました:${error.message}`;
console.error(error);
} finally {
setLoading(false);
}
}
JavaScriptここでも、上から順に読むとストーリーになっています。
金額をチェックする。
変換したい通貨のリストを決める。
ローディングを開始する。
複数通貨の変換を同時に行う。
成功したら state に保存し、一覧表示を行う。
失敗したらエラーメッセージを出す。
最後にローディングを終了する。
Promise.all の存在を意識しなくても、
「複数通貨の変換をまとめて行う関数」として読めるのが理想です。
ローディング表示と state を“アプリ全体のルール”にする
setLoading の完成形
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ここで大事なのは、
「ローディング中は、ユーザーが混乱しそうな操作を全部止める」
というルールをコードにしていることです。
単一通貨変換でも、複数通貨変換でも、
setLoading(true, …) と setLoading(false) を呼ぶだけで、
同じ体験が提供されます。
エラーハンドリングを“責任の場所”で分けて考える
7日目では、エラーハンドリングをもう一段整理しておきます。
入力エラーは parseAmount の責任です。
ここでは「金額が空か」「数値か」「0 より大きいか」「大きすぎないか」をチェックし、
ダメなら ok: false とメッセージを返します。
HTTP エラーとネットワークエラーは requestJson の責任です。
ここではステータスコードを見て、
500 番台なら「サーバー側の問題」、
それ以外なら一般的な HTTP エラーとして Error を投げます。
API レベルの失敗とデータ形式エラーは convertViaAPI の責任です。
success が false なら「レートの取得に失敗しました」、
result が数値でなければ「予期しない形式」として Error を投げます。
UI 側の関数(convertCurrency や convertToPopularCurrencies)は、
try の中で「うまくいったときのストーリー」を書き、
catch で error.message をユーザー向けに表示します。
この分担ができていると、
「どの種類のエラーがどこで処理されているか」が明確になります。
結果として、コードが直しやすく、拡張しやすくなります。
7日目のまとめと、あなたがもう持っている力
ここまでで、ExchangeRate.host 通貨変換アプリはこういう姿になりました。
単一通貨の変換ができる。
逆変換ボタンで from / to を一瞬で入れ替えられる。
プリセット通貨ペアで、よく使う組み合わせをワンクリックで選べる。
金額入力のチェックがしっかりしている。
ローディング表示が一貫して動く。
入力エラー、HTTP エラー、API エラー、データ形式エラー、ネットワークエラーを、原因別に扱えている。
お気に入り通貨ペアを保存し、localStorage に永続化できる。
お気に入りから再変換できる。
複数通貨への一括変換を Promise.all で同時に行い、一覧表示できる。
state を中心に、fetch、UI、イベントが整理されている。
fetch は requestJson に共通化され、ExchangeRate.host 専用の convertViaAPI によって API 仕様が一箇所に集約されている。
これらはすべて
fetch / async‑await / エラーハンドリング
という 3 つの柱の上に成り立っています。
今日いちばん伝えたいのは、
「ここまで来たあなたは、もう“どんな API でも同じ型で扱える”ところまで来ている」
ということです。
URL が違うだけ。
返ってくる JSON の形が違うだけ。
やっていることの本質は、為替レートでも、祝日でも、天気でも、翻訳でも同じです。
fetch で取りに行く。
async/await で待つ。
try/catch で失敗を受け止める。
state に反映する。
UI に描画する。
この流れを、自分の言葉で説明できるようになっていたら、
もう「API が怖い初心者」ではありません。
ここから先は、
あなたが作りたいアプリに合わせて、
この型の上に機能を積み上げていくだけです。

