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

APP JavaScript
スポンサーリンク

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

6日目のテーマは
「Datamuse アプリの“中身の設計”を整えて、コードを読める・直せる・拡張できる状態にする」
ことです。

技術キーワードはいつも通りです。

  • fetch
  • Promise / async-await
  • エラーハンドリング

ただし今日は、これらを「書ける」から一歩進めて、

  • 処理を役割ごとに分ける(責務分離)
  • fetch 周りのコードを“パターン”として固める
  • 通信失敗時の分岐を、読みやすい形に整理する
  • 小さな機能追加をしながら、設計の大事さを体感する

という「中級者の視点」を意識していきます。


まず“責務”でコードを分けて考える

4つの役割に分解してみる

Datamuse アプリのコードは、ざっくり次の 4 つに分けられます。

  1. 状態管理(state)
  2. API 通信(fetch / async/await / エラーハンドリング)
  3. UI 更新(render 系の関数)
  4. イベント処理(クリック・入力などのきっかけ)

6日目では、この 4 つを意識してコードを整理していきます。


状態管理を“見える化”する

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

ここでのポイントは、

  • 「今アプリがどういう状態か」を state を見るだけで把握できる
  • どこからでも同じ方法で状態を更新できる

という“見通しの良さ”です。


API 通信部分を「完全にパターン化」する

Datamuse 用の共通関数を最終形に近づける

6日目では、requestDatamuse
「どの機能からも安心して呼べる土台」にします。

async function requestDatamuse(params) {
  const baseUrl = "https://api.datamuse.com/words";
  const url = `${baseUrl}?${params.toString()}`;

  let response;

  try {
    response = await fetch(url);
  } catch (networkError) {
    throw new Error("ネットワークエラーが発生しました。接続を確認してください。");
  }

  if (!response.ok) {
    throw new Error(`HTTPエラー(${response.status})が発生しました。`);
  }

  let data;

  try {
    data = await response.json();
  } catch (parseError) {
    throw new Error("レスポンスの解析に失敗しました。");
  }

  if (!Array.isArray(data)) {
    throw new Error("予期しないレスポンス形式です。");
  }

  return data;
}
JavaScript

ここで深掘りしたいのは、

  • fetch 自体の失敗(ネットワーク)
  • HTTP ステータスエラー(4xx / 5xx)
  • JSON パースエラー
  • データ形式エラー

をすべて「メッセージ付きの Error」として上に投げていることです。

これで、上位の関数は
「何をしようとして失敗したか」だけを足してユーザーに伝えればよくなる
という状態になります。


検索処理を“読みやすい流れ”に整える

URL を組み立てる部分を分離する

検索用のパラメータを作る関数を用意します。

function buildSearchParams(word, mode) {
  const params = new URLSearchParams();

  if (mode === "ml") {
    params.set("ml", word);
  } else if (mode === "rel_trg") {
    params.set("rel_trg", word);
  } else if (mode === "rel_rhy") {
    params.set("rel_rhy", word);
  } else {
    params.set("ml", word);
  }

  params.set("max", "20");

  return params;
}
JavaScript

検索のメイン関数を“ストーリー”として読む

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

この関数は、上から順に読むとこういう“物語”になっています。

  1. すでにローディング中なら何もしない
  2. ローディング状態にして、画面をクリア
  3. パラメータを作って Datamuse にリクエスト
  4. 結果が空なら「見つからなかった」と伝える
  5. 結果をスコア順に並べて表示
  6. 履歴と最近を更新
  7. どこかでエラーが起きたら、モード名付きでメッセージ表示
  8. 最後にローディング状態を解除

ここでの大事なポイントは、
「fetch の細かいエラー処理は requestDatamuse に隠している」
ということです。


ローディング表示とエラー表示を“パターン”として固定する

ステータス表示の関数を使い切る

6日目では、ステータス表示を完全に関数に寄せます。

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");
}
JavaScript

CSS で見た目を揃えます。

.status.loading { color: #555; }
.status.success { color: #0a0; }
.status.error { color: #c00; }

ローディング開始・終了を state と連動させる

function startLoading(message) {
  updateState({ isLoading: true });
  showLoading(message || "取得中です…");
  searchButton.disabled = true;
}

function endLoading() {
  updateState({ isLoading: false });
  searchButton.disabled = false;
}
JavaScript

これで、

  • 「ローディング中かどうか」は state.isLoading
  • 見た目は showLoading / showSuccess / showError
  • ボタンの無効化も startLoading / endLoading に集約

という“お作法”が固まります。


通信失敗時の分岐を“用途ごと”に整理する

メイン検索とサジェストで重みを変える

メイン検索はユーザーにとって重要なので、
失敗したらしっかりメッセージを出します。

} 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

ここでの深掘りポイントは、

  • すべてのエラーを同じように扱わなくていい
  • 「ユーザーにとってどれくらい重要な処理か」でメッセージの重さを変える

という“エラーハンドリングの設計”です。


小さな機能追加で「設計の良さ」を体感する

例:モードごとの「デフォルトメッセージ」を足してみる

モードごとに、
「こういうときに使うといいよ」という説明を出したくなったとします。

function getModeDescription(mode) {
  if (mode === "ml") return "類義語:意味が近い単語を探します。";
  if (mode === "rel_trg") return "連想語:その単語から連想される単語を探します。";
  if (mode === "rel_rhy") return "韻:語尾の音が似ている単語を探します。";
  return "";
}
JavaScript

検索成功時に、
ステータスに一言足すこともできます。

const label = getModeShortLabel(mode);
const desc = getModeDescription(mode);
showSuccess(`${label}の取得に成功しました。 ${desc}`);
JavaScript

ここで気づいてほしいのは、
「fetch の型をいじらなくても、機能はどんどん足せる」
ということです。

設計が整理されていると、
“どこに何を足せばいいか”が自然に見えてきます。


6日目のまとめ

6日目でやったことを整理すると、こうなります。

  • state にアプリの状態を集約し、updateState で一元管理
  • requestDatamuse に fetch・async/await・エラーハンドリングを集約
  • fetchWordsForMode を「ストーリーとして読める形」に整理
  • ローディング・成功・エラー表示を関数と CSS でパターン化
  • メイン検索とサジェストでエラーの“重さ”を変える
  • 小さな機能追加(説明文など)を通して、設計の良さを体感

どれも新しい文法ではなく、
「同じ fetch / async/await / エラーハンドリングを、どう整理して使うか」
という話です。


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

6日目の本質は、

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

fetch
async/await
try-catch
エラーメッセージ
ローディング状態
state

これらはもう、あなたの中でバラバラではなく、
ひとつの“型”としてまとまり始めています。

7日目は、この Datamuse アプリを
「中級編の完成形」としてまとめていく回にしよう。
そのとき、今日整えた設計が、ものすごく効いてきます。

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