4日目のゴールと今日やること
4日目のテーマは
「fetch・async/await・エラーハンドリングの“型”はそのままに、Datamuse アプリを“学習ツール”として育てる」
ことです。
ここまでであなたはすでに、
- モード切り替え(類義語 / 連想語 / 韻)
- お気に入り単語
- 入力補完(前方一致サジェスト)
- 共通の
requestDatamuse関数
といった「API 通信アプリの中核」を手に入れました。
4日目はここに、
- 検索履歴
- 最近見た単語
- ローディング表示の整理
- エラーハンドリングの“パターン化”
を足して、「語彙を増やすために毎日使えるツール」に近づけていきます。
今日のアプリのイメージを先に描く
どんな機能を足すか
4日目で目指すアプリは、こんな動きをします。
- 単語+モード(類義語 / 連想語 / 韻)で検索
- 結果はスコア順に表示
- 単語をお気に入りに追加できる
- 検索履歴が残り、クリックで再検索できる
- 最近検索した単語一覧が表示される
- ローディング中は状態が一目でわかる
- 通信失敗時のメッセージが「パターン」として整理されている
fetch・async/await・エラーハンドリングの“型”は変えません。
変えるのは「状態」と「UI の振る舞い」です。
状態を「ひとまとめ」にして迷子を防ぐ
state オブジェクトを導入する
状態が増えてきたので、
バラバラの変数ではなく、オブジェクトにまとめます。
const state = {
word: "",
mode: "ml",
isLoading: false,
favorites: [],
history: [],
recent: []
};
JavaScriptここに「アプリが覚えておくべきこと」を全部入れていきます。
状態を更新する小さな関数
function updateState(updates) {
Object.assign(state, updates);
}
JavaScript例えば、検索開始時はこう書けます。
updateState({
word,
mode,
isLoading: true
});
JavaScript「状態を一箇所に集める」ことで、
“今アプリがどういう状況なのか” が追いやすくなります。
検索履歴を実装する
履歴のルールを決める
履歴は、こういうルールにします。
- 単語+モードの組み合わせで 1 件
- 新しいものが先頭
- 重複は上書き(前にあったものは削除して先頭に追加)
- 最大 10 件まで
履歴を追加する関数
function addHistory(word, mode) {
const key = `${word}::${mode}`;
state.history = state.history.filter((item) => item.key !== key);
state.history.unshift({ key, word, mode });
if (state.history.length > 10) {
state.history.pop();
}
renderHistory();
}
JavaScriptここでのポイントは、
- 「単語だけ」ではなく「モードも含めて履歴にする」
- 同じ条件は 1 件だけにする
という「履歴の一意性」です。
履歴の表示
const historyDiv = document.getElementById("history");
function renderHistory() {
if (state.history.length === 0) {
historyDiv.textContent = "検索履歴はまだありません。";
return;
}
let html = "<h3>検索履歴</h3>";
state.history.forEach((item) => {
const label = getModeShortLabel(item.mode);
html += `
<button class="history-item" data-word="${item.word}" data-mode="${item.mode}">
${item.word}(${label})
</button>
`;
});
historyDiv.innerHTML = html;
const buttons = historyDiv.querySelectorAll(".history-item");
buttons.forEach((btn) => {
btn.addEventListener("click", () => {
const word = btn.dataset.word;
const mode = btn.dataset.mode;
wordInput.value = word;
setModeRadio(mode);
updateState({ word, mode });
fetchWordsForMode(word, mode);
});
});
}
JavaScriptsetModeRadio はラジオボタンを切り替える小さな関数です。
function setModeRadio(mode) {
const radios = document.querySelectorAll('input[name="mode"]');
radios.forEach((r) => {
r.checked = r.value === mode;
});
}
JavaScript最近検索した単語を記録する
「履歴」と「最近」はどう違う?
履歴は「条件(単語+モード)」の記録。
最近は「単語だけ」の記録として扱います。
例えば、
- happy(類義語)
- happy(韻)
は履歴では 2 件ですが、
最近では「happy」として 1 件でいい、という考え方もできます。
ここではシンプルに「単語だけのリスト」として持ちます。
最近リストの更新
const recentDiv = document.getElementById("recent");
function addRecent(word) {
state.recent = state.recent.filter((w) => w !== word);
state.recent.unshift(word);
if (state.recent.length > 5) {
state.recent.pop();
}
renderRecent();
}
function renderRecent() {
if (state.recent.length === 0) {
recentDiv.textContent = "最近検索した単語はありません。";
return;
}
let html = "<h3>最近検索した単語</h3>";
state.recent.forEach((word) => {
html += `<button class="recent-item" data-word="${word}">${word}</button>`;
});
recentDiv.innerHTML = html;
const buttons = recentDiv.querySelectorAll(".recent-item");
buttons.forEach((btn) => {
btn.addEventListener("click", () => {
const word = btn.dataset.word;
wordInput.value = word;
updateState({ word });
});
});
}
JavaScript検索成功時に、addHistory(word, mode) と addRecent(word) を呼びます。
ローディング表示を「パターン」として整理する
ステータス表示を一元管理する
ステータス表示を関数にまとめます。
function setStatus(message, type) {
statusDiv.textContent = message;
statusDiv.className = "status " + type;
}
function showLoading(message) {
setStatus(message || "取得中です…", "loading");
}
function showSuccess(message) {
setStatus(message, "success");
}
function showError(message) {
setStatus(message, "error");
}
JavaScriptCSS 側で色を変えます。
.status.loading { color: #555; }
.status.success { color: #0a0; }
.status.error { color: #c00; }
ローディング開始・終了を整理する
function startLoading(message) {
updateState({ isLoading: true });
showLoading(message || "取得中です…");
searchButton.disabled = true;
}
function endLoading() {
updateState({ isLoading: false });
searchButton.disabled = false;
}
JavaScriptfetchWordsForMode はこうなります。
async function fetchWordsForMode(word, mode) {
if (state.isLoading) return;
startLoading("単語を取得中です…");
resultDiv.textContent = "";
try {
const params = buildSearchParams(word, mode);
const data = await requestDatamuse(params);
if (data.length === 0) {
const label = getModeShortLabel(mode);
showError(`${label}が見つかりませんでした。`);
resultDiv.textContent = "";
return;
}
const sorted = [...data].sort((a, b) => (b.score || 0) - (a.score || 0));
const label = getModeShortLabel(mode);
showSuccess(`${label}の取得に成功しました。`);
renderWords(sorted, mode);
addHistory(word, mode);
addRecent(word);
} catch (error) {
const label = getModeShortLabel(mode);
showError(`${label}の取得中にエラーが発生しました:${error.message}`);
console.error(error);
} finally {
endLoading();
}
}
JavaScriptここでの深掘りポイントは、
「ローディング・成功・エラーの見せ方を“関数”として固定する」
ことです。
これをやっておくと、
どんな API アプリでも「見た目のルール」が揃います。
エラーハンドリングを「パターン」としてまとめる
エラーの種類を意識する
Datamuse の場合でも、エラーはざっくりこう分けられます。
- ネットワークエラー(fetch 自体が失敗)
- HTTP エラー(response.ok が false)
- データ形式エラー(配列じゃないなど)
requestDatamuse の中で、
HTTP エラーと形式エラーはすでに throw しています。
async function requestDatamuse(params) {
const baseUrl = "https://api.datamuse.com/words";
const url = `${baseUrl}?${params.toString()}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTPエラー(${response.status})`);
}
const data = await response.json();
if (!Array.isArray(data)) {
throw new Error("予期しないレスポンス形式です。");
}
return data;
}
JavaScriptこれにより、fetchWordsForMode 側では「まとめて catch」できます。
} catch (error) {
const label = getModeShortLabel(mode);
showError(`${label}の取得中にエラーが発生しました:${error.message}`);
}
JavaScriptサジェスト側のエラーは「静かに処理」
サジェストは「補助機能」なので、
失敗しても画面を赤くする必要はありません。
async function fetchSuggestions(prefix) {
if (!prefix) {
suggestDiv.textContent = "";
return;
}
const params = new URLSearchParams();
params.set("sp", prefix + "*");
params.set("max", "5");
try {
const data = await requestDatamuse(params);
if (data.length === 0) {
suggestDiv.textContent = "";
return;
}
renderSuggestions(data);
} catch (error) {
console.error("サジェスト取得中にエラー", error);
suggestDiv.textContent = "";
}
}
JavaScriptここでのポイントは、
「全部のエラーを同じ重さで扱わない」
ということです。
4日目の全体像(重要部分のコードイメージ)
細部はあなたの 1〜3 日目のコードに合わせて調整してほしいですが、
構造としてはこうなります。
stateに word / mode / isLoading / favorites / history / recentrequestDatamuseで fetch・async/await・エラーハンドリングを共通化fetchWordsForModeで検索ロジック+履歴・最近更新addHistory/renderHistoryで履歴機能addRecent/renderRecentで最近機能setStatus/showLoading/showSuccess/showErrorで表示パターン統一
今日いちばん深く理解してほしいこと
4日目の本質は、
「fetch・async/await・エラーハンドリングの“型”はもう完成している。
これからの差は“状態をどう持ち、どう見せるか”で決まる」
ということです。
単語
モード
お気に入り
履歴
最近
ローディング状態
これらは全部「状態」です。
状態を整理して、
UI に反映する関数を分けて、
エラーメッセージのパターンを決める。
それができるようになると、
どんな API でも「自分のツール」に変えていけます。


