5日目のゴールと今日やること
5日目のテーマは
「Datamuse アプリの“中身”を整理して、fetch / async-await / エラーハンドリングを設計として理解する」
ことです。
ここまでであなたは、
単語検索・モード切り替え・サジェスト・履歴・お気に入り・ローカルストレージ
まで作ってきました。
今日はそこから一歩進めて、
- fetch を共通関数にして「どこからでも同じ型で使える」ようにする
- Promise / async-await を使って「複数の API を同時に叩く」体験をする
- エラーハンドリングを「原因別」に整理する
- ローディング表示を「複数リクエスト」に対応させる
という、まさに中級者らしい内容に入っていきます。
新しい文法はほぼ出てきません。
今までやってきたことを「一段抽象度を上げて」扱う回です。
fetch を「共通関数」にしてしまう発想
なぜ毎回 fetch を直書きしない方がいいのか
今のコードは、
本検索・サジェストなど、いろいろな場所で fetch を書いています。
どこもだいたい同じ形です。
const response = await fetch(url);
if (!response.ok) { … }
const data = await response.json();
JavaScriptこれを毎回書くと、
- エラーハンドリングの書き方がバラバラになる
- 修正したいときに、全部の fetch を直さないといけない
- 「fetch のお作法」がコード中に散らばる
という状態になります。
そこで、
「JSON を返す API を叩くための共通関数」
をひとつ作ってしまいます。
共通の requestJson 関数を作る
まずは素直な形で書いてみる
async function requestJson(url, options = {}) {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTPエラー(${response.status})`);
}
const data = await response.json();
return data;
} catch (error) {
throw new Error(error.message);
}
}
JavaScriptこの関数は、
「URL とオプションを受け取って、JSON を返す」
という役割だけを持っています。
ポイントは、
- fetch 自体の失敗(ネットワークエラー)
- HTTP エラー(404, 500 など)
- JSON パースエラー
を全部まとめて throw new Error(...) にしていることです。
これにより、
上の階層の関数は「成功したか・失敗したか」だけを扱えばよくなります。
Datamuse 専用の「検索関数」を作る
本検索を requestJson の上に乗せる
今までの本検索は、
fetch を直接書いていました。
これを、
requestJson を使う形に変えます。
async function searchDatamuse(word, mode) {
const url = `https://api.datamuse.com/words?${mode}=${encodeURIComponent(word)}`;
const data = await requestJson(url);
if (!Array.isArray(data)) {
throw new Error("予期しない形式のデータが返されました。");
}
if (data.length === 0) {
throw new Error("該当する単語が見つかりませんでした。");
}
const sorted = [...data].sort((a, b) => (b.score || 0) - (a.score || 0));
return sorted;
}
JavaScriptここでの深掘りポイントは、
- requestJson が「通信まわり」を担当
- searchDatamuse が「Datamuse の仕様チェック」を担当
というふうに、
責務を分けていることです。
UI 側の関数を「ストーリーとして読める」形にする
fetchWords を“ほぼ日本語”にする
共通関数を作ると、
UI 側の関数が一気に読みやすくなります。
async function fetchWords() {
const word = wordInput.value.trim();
const mode = modeSelect.value;
if (!word) {
statusDiv.textContent = "単語を入力してください。";
resultDiv.textContent = "";
return;
}
const modeLabel = getModeLabel(mode);
startLoading(`${modeLabel}を取得中です…`);
resultDiv.textContent = "";
try {
const words = await searchDatamuse(word, mode);
statusDiv.textContent = `${modeLabel}の取得に成功しました。`;
renderWords(words, modeLabel);
addHistory(word, mode);
} catch (error) {
statusDiv.textContent = `検索中にエラーが発生しました:${error.message}`;
console.error(error);
} finally {
endLoading();
}
}
JavaScript上から読むと、ほぼ物語です。
単語をチェックして
モード名を決めて
ローディングを開始して
Datamuse に検索を投げて
成功したら表示して履歴に残して
失敗したらエラーメッセージを出して
最後にローディングを止める
fetch の細かい話は、searchDatamuse と requestJson に隠れています。
Promise.all で「複数モードを一気に検索」してみる
複数の API を同時に叩くイメージ
ここから少しだけ、
Promise / async-await の“本気”を見てみます。
例えば、
「ある単語について、意味が近い単語と連想語を同時に知りたい」
というケースを考えます。
やりたいことはこうです。
ml=wordとrel_trg=wordを同時に投げる- 両方の結果がそろったら画面に表示する
- どちらかが失敗したら、エラーメッセージを出す
これを実現するのが Promise.all です。
Promise.all を async/await で使う
複数モード検索の関数
async function searchMultipleModes(word) {
const promises = [
searchDatamuse(word, "ml"),
searchDatamuse(word, "rel_trg")
];
const [similar, related] = await Promise.all(promises);
return { similar, related };
}
JavaScriptここでのポイントは、
- searchDatamuse は Promise(非同期処理)を返す
- Promise.all は「全部終わるまで待つ」
- 結果は配列で返ってくるので、分割代入で受け取る
という流れです。
複数モード検索を UI に組み込む
ボタンをひとつ増やすイメージ
例えば、
「まとめて検索」ボタンを用意して、
こういう関数をつなぎます。
async function fetchMultiple() {
const word = wordInput.value.trim();
if (!word) {
statusDiv.textContent = "単語を入力してください。";
resultDiv.textContent = "";
return;
}
startLoading("複数モードで検索中です…");
resultDiv.textContent = "";
try {
const { similar, related } = await searchMultipleModes(word);
statusDiv.textContent = "複数モードの取得に成功しました。";
renderMultipleResults(similar, related);
addHistory(word, "multi");
} catch (error) {
statusDiv.textContent = `複数モード検索中にエラーが発生しました:${error.message}`;
console.error(error);
} finally {
endLoading();
}
}
JavaScript複数モードの結果を表示する
renderMultipleResults の例
function renderMultipleResults(similar, related) {
let html = "<h3>複数モード検索結果</h3>";
html += "<h4>意味が近い単語</h4>";
similar.forEach((item) => {
html += `<p>${item.word}(スコア: ${item.score})</p>`;
});
html += "<h4>連想される単語</h4>";
related.forEach((item) => {
html += `<p>${item.word}(スコア: ${item.score})</p>`;
});
resultDiv.innerHTML = html;
}
JavaScriptここで大事なのは、
「Promise.all を使っても、UI 側の構造は変わらない」
という感覚です。
やっていることは、
- 非同期処理を待つ
- 結果を UI に反映する
という、いつものパターンの延長です。
エラーハンドリングを「原因別」に整理する
requestJson を少し賢くする
今の requestJson は、
HTTP ステータスをまとめて HTTPエラー(コード) にしています。
ここを、
少しだけ「原因別」にしてみます。
async function requestJson(url, options = {}) {
try {
const response = await fetch(url, options);
if (!response.ok) {
if (response.status === 429) {
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ここでの深掘りポイントは、
- 429(Too Many Requests)を特別扱いしている
- 500 番台を「サーバー側の問題」として扱っている
- それ以外は汎用的なメッセージにしている
というところです。
ユーザーにとっては、
「自分が悪いのか、サーバーが悪いのか」
がわかるだけで、ストレスがかなり減ります。
ローディング表示を「複数リクエスト」に対応させる
ローディング中かどうかを state で管理する
複数モード検索などをすると、
「今どのリクエストが動いているのか」がわかりにくくなります。
そこで、
ローディング状態を state に持たせておきます。
const state = {
isLoading: false,
history: [],
favorites: []
};
JavaScriptそして、
startLoading / endLoading で更新します。
function startLoading(message) {
updateState({ isLoading: true });
statusDiv.textContent = message || "処理中です…";
searchButton.disabled = true;
multiButton.disabled = true;
}
function endLoading() {
updateState({ isLoading: false });
searchButton.disabled = false;
multiButton.disabled = false;
}
JavaScriptこれで、
- 単一モード検索
- 複数モード検索
どちらでも、
「ローディング中はボタンが無効になる」
という一貫した動きになります。
5日目のまとめ
5日目でやったことを、言葉で整理してみます。
fetch を共通関数 requestJson にまとめて、
「通信まわりのエラーハンドリング」を一箇所に集約した。
Datamuse 専用の searchDatamuse を作り、
API の仕様チェック(配列かどうか、件数があるかなど)をそこで行うようにした。
UI 側の fetchWords は、
「入力チェック → ローディング → searchDatamuse → 表示 → 履歴 → ローディング終了」
という“ストーリーとして読める”形になった。
Promise.all を使って、
複数モードを同時に検索する searchMultipleModes を作った。
複数モード検索でも、
結局は「非同期処理を待って UI に反映する」という
いつものパターンの延長であることを確認した。
requestJson の中で、
429 や 500 番台などを「原因別」に扱うことで、
ユーザーにとってわかりやすいエラーメッセージにした。
ローディング状態を state に持たせ、
単一検索・複数検索どちらでも一貫した UI にした。
今日いちばん深く理解してほしいこと
5日目の本質は、
「fetch / async/await / try-catch は、“型”として設計してしまえば怖くない」
ということです。
単一検索
サジェスト
履歴
お気に入り
複数モード検索
これらは全部、
同じ型の上に乗っています。
6日目・7日目では、
この Datamuse アプリを「中級編の完成形」としてまとめながら、
あなたの中に “API アプリの設計パターン” をしっかり定着させていきます。


