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

APP JavaScript
スポンサーリンク

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

2日目のテーマは
「1日目で作った Datamuse ミニアプリを、“ちゃんと使えるツール”に近づける」 ことです。

1日目では、
単語を入力 → モードを選ぶ → Datamuse API に fetch → 結果を表示
という「最低限動く」アプリを作りました。

2日目では、そこから一歩進めて、

  • fetch と async/await を“パターン”として固める
  • エラーハンドリングを少し丁寧にする
  • ローディング表示を改善する
  • 検索モードを増やして「アプリらしさ」を出す

というところを狙っていきます。

「新しい文法を覚える」というより、
昨日やったことを“設計として整理する” イメージです。


Datamuse API の「モード」をちゃんと理解する

よく使う 3 つのモード

Datamuse API は、クエリパラメータによって動きが変わります。
中級編でよく使うのは、このあたりです。

ml=
意味が近い単語(類義語)を返す。
例:?ml=happy

rel_trg=
その単語から連想される単語を返す。
例:?rel_trg=dog

rel_rhy=
韻を踏む単語を返す。
例:?rel_rhy=time

1日目では ?ml=word のように 1 パターンだけ使いましたが、
2日目では「モードを切り替えられる UI」を前提にして考えます。


1日目のコードを「2日目仕様」にアップデートする

HTML のイメージ(モードを増やす)

モード選択を、こういう感じにしておきます。

<input id="wordInput" placeholder="単語を入力" />

<select id="modeSelect">
  <option value="ml">意味が近い単語(類義語)</option>
  <option value="rel_trg">連想される単語</option>
  <option value="rel_rhy">韻を踏む単語</option>
</select>

<button id="searchButton">検索</button>

<div id="status"></div>
<div id="result"></div>

JavaScript 側では、
modeSelect.valueml / rel_trg / rel_rhy のいずれかになります。


fetch の「型」を少しだけ整える

1日目の fetch を思い出す

1日目では、だいたいこんな形でした。

const url = `https://api.datamuse.com/words?${mode}=${word}`;
const response = await fetch(url);
const data = await response.json();
JavaScript

これでも動きますが、
2日目では「エラーのこともちゃんと考えた fetch」にしていきます。


エラーハンドリングを 3 段階で考える

API 通信のエラーは、ざっくり 3 つに分けられます。

1つ目は、ネットワークエラー。
Wi-Fi が切れている、サーバーにそもそも届かない、などのケースです。
これは fetch 自体が失敗して catch に入ります。

2つ目は、HTTP エラー。
サーバーには届いたけれど、ステータスコードが 200 ではない場合です。
response.ok が false になります。

3つ目は、データの中身が想定外。
JSON は返ってきたけれど、配列じゃない、空配列だった、必要なプロパティがない、などです。

この 3 段階を意識して、
「どこで何をチェックするか」 を整理していきます。


ローディング表示を「ちゃんとしたパターン」にする

startLoading / endLoading を少し強化する

1日目では、こんな感じでした。

function startLoading() {
  statusDiv.textContent = "取得中です…";
  searchButton.disabled = true;
}

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

2日目では、「メッセージを変えられるようにする」と便利です。

function startLoading(message) {
  statusDiv.textContent = message || "取得中です…";
  searchButton.disabled = true;
}

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

こうしておくと、
「類義語を取得中…」「連想語を取得中…」など、
モードに応じたメッセージを出せるようになります。


モードごとにメッセージを変える小さな工夫

モード名を人間向けのラベルに変換する

API のパラメータ mlrel_trg は、
人間にはわかりにくいので、
「表示用の名前」に変換する関数を作ります。

function getModeLabel(mode) {
  if (mode === "ml") return "意味が近い単語";
  if (mode === "rel_trg") return "連想される単語";
  if (mode === "rel_rhy") return "韻を踏む単語";
  return "関連語";
}
JavaScript

これを使うと、
ステータスメッセージが一気にわかりやすくなります。


2日目版 fetchWords の全体像

ここからが今日のメインです。
1日目の fetchWords を、2日目仕様に書き直してみます。

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 url = `https://api.datamuse.com/words?${mode}=${encodeURIComponent(word)}`;
    const response = await fetch(url);

    if (!response.ok) {
      statusDiv.textContent = `サーバーエラーが発生しました。(${response.status})`;
      return;
    }

    const data = await response.json();

    if (!Array.isArray(data)) {
      statusDiv.textContent = "予期しない形式のデータが返されました。";
      console.error("Unexpected data:", data);
      return;
    }

    if (data.length === 0) {
      statusDiv.textContent = `${modeLabel}が見つかりませんでした。`;
      resultDiv.textContent = "";
      return;
    }

    statusDiv.textContent = `${modeLabel}の取得に成功しました。`;
    renderWords(data, modeLabel);

  } catch (error) {
    statusDiv.textContent = "通信に失敗しました。ネットワークを確認してください。";
    console.error(error);

  } finally {
    endLoading();
  }
}
JavaScript

ここで、重要なポイントをひとつずつ分解していきます。


重要ポイント1:encodeURIComponent を使う理由

const url = `https://api.datamuse.com/words?${mode}=${encodeURIComponent(word)}`;
JavaScript

ここで encodeURIComponent を使っているのは、
ユーザーがスペースや日本語を入力したときに、
URL として正しく送るためです。

例えば、
ice creamありがとう のような文字列は、
そのままだと URL として不正になる可能性があります。

encodeURIComponent は、
「URL の中で特別な意味を持つ文字を、安全な形に変換する」
ための関数です。


重要ポイント2:Array.isArray で形式をチェックする

if (!Array.isArray(data)) {
  statusDiv.textContent = "予期しない形式のデータが返されました。";
  console.error("Unexpected data:", data);
  return;
}
JavaScript

Datamuse API は、通常は配列を返します。
しかし、何かの理由で仕様が変わったり、
プロキシや別のサーバーを経由したりすると、
配列ではないものが返ってくる可能性もゼロではありません。

ここで形式をチェックしておくと、
「おかしなデータが来たときに、原因を追いやすくなる」
というメリットがあります。


重要ポイント3:「該当なし」と「エラー」を分ける

if (data.length === 0) {
  statusDiv.textContent = `${modeLabel}が見つかりませんでした。`;
  resultDiv.textContent = "";
  return;
}
JavaScript

ここがとても大事です。

「データが 0 件だった」というのは、
エラーではなく“正常な結果” です。

  • 通信は成功している
  • サーバーも正常に動いている
  • ただ、その単語に対応する結果がなかった

というだけなので、
「エラーが起きました」ではなく、
「見つかりませんでした」と伝えるのが正解です。


重要ポイント4:catch では「技術用語をそのまま出さない」

} catch (error) {
  statusDiv.textContent = "通信に失敗しました。ネットワークを確認してください。";
  console.error(error);
}
JavaScript

ここで、
statusDiv.textContent = error.message;
のようにしてしまうと、
ユーザーには意味がわからない英語のメッセージが出ることがあります。

ユーザーに見せるメッセージは、
「状況がイメージできる日本語」 にするのが大事です。

一方で、
console.error(error);
で開発者向けの情報はちゃんと残しておきます。


結果表示を少しだけリッチにする

スコア順に並べて表示する

Datamuse のレスポンスには score という数値が入っています。
これは「どれくらい関連度が高いか」の指標です。

2日目では、
この score を使って並べ替えてみましょう。

function renderWords(data, modeLabel) {
  const sorted = [...data].sort((a, b) => (b.score || 0) - (a.score || 0));

  let html = `<h3>${modeLabel}の結果</h3>`;

  sorted.forEach((item) => {
    const scoreText = item.score != null ? `(スコア: ${item.score})` : "";
    html += `<p>${item.word}${scoreText}</p>`;
  });

  resultDiv.innerHTML = html;
}
JavaScript

ここでのポイントは、
[...data] として元の配列をコピーしてから sort していることです。
元の配列を直接いじらないことで、
「副作用の少ないコード」になります。


イベント登録は 1 行でシンプルに

searchButton.addEventListener("click", fetchWords);
JavaScript

ここは 1日目と同じですが、
「ボタンを押したら何が起きるか」が
1 行で読み取れる のは、とても大事です。


2日目のまとめ

2日目でやったことを、言葉で整理してみます。

Datamuse API のモードを理解して、
ml / rel_trg / rel_rhy を UI から切り替えられるようにした。

fetch の前後に、

  • 入力チェック
  • ローディング開始
  • HTTP ステータスチェック
  • データ形式チェック
  • 件数 0 のときの分岐
  • 成功時の表示
  • 通信失敗時のメッセージ
  • ローディング終了

という「一連の流れ」をきれいに並べた。

エラーハンドリングを、

  • ネットワークエラー(catch)
  • HTTP エラー(response.ok)
  • データ形式エラー(Array.isArray)
  • 該当なし(length === 0)

に分けて考えた。

そして、
「ユーザーに見せるメッセージ」と「開発者が見るログ」を分けた。

これらは全部、
fetch / async-await / try-catch を
「雑に書く」のではなく
「設計として扱い始めた」 ということです。


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

2日目の本質は、

「同じ fetch でも、“どう扱うか”でアプリの質が変わる」

ということです。

まったく同じ Datamuse API を叩いていても、

  • エラーと該当なしを分けているか
  • モード名を人間向けに変換しているか
  • ローディング表示があるか
  • メッセージが日本語でわかりやすいか

これだけで、
「ただ動くだけのサンプル」と
「ちゃんとしたアプリ」の差が生まれます。

3日目では、
この Datamuse アプリに「入力補助」「サジェスト」「履歴」などを足して、
さらに“アプリらしさ”を育てていきます。

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