Ajax とは何か

「Ajax」という言葉を聞いて「古い技術」と思うなら、それは半分正しく半分間違いです。Ajax(Asynchronous JavaScript and XML)は特定のライブラリや API の名前ではなく、「ページ全体をリロードせずにサーバーと通信し、一部だけを更新する」というアーキテクチャパターンを指します。

2005 年に Jesse James Garrett が命名したこのパターンは、当時の Web 開発に革命をもたらした。それまでの Web はフォームを送信するたびにページ全体が再読み込みされていた。Ajax によって Gmail や Google Maps のような「アプリに近い」体験が可能になった。

Ajax を最初に実現した手段が XMLHttpRequest(XHR)です。2006 年に W3C が標準化したこの API は、名前に「XML」と入っているものの、実際には JSON やテキストなど何でも送受信できます。

XHR の書き方を一度見ておく

XHR を使った素のコードはこう書く。

const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data');
xhr.onload = function () {
  if (xhr.status === 200) {
    const data = JSON.parse(xhr.responseText);
    console.log(data);
  } else {
    console.error('Error:', xhr.status);
  }
};
xhr.onerror = function () {
  console.error('Network error');
};
xhr.send();

コールバックが入れ子になり、エラー分岐も手動で書く必要がある。POST にヘッダーを付けたり、複数リクエストを直列に並べたりするとさらに複雑になる。このコールバック地獄を解消するために Promise ベースの API が求められました。それが fetch です。

fetch の基本

fetch は 2015 年頃にブラウザに実装された新しい HTTP クライアント API で、Promise を返す。

fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(err => console.error(err));

async/await で書き換えるとさらに読みやすくなる。

async function fetchData() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  console.log(data);
}

処理が上から下へ一直線に流れ、同期コードと同じように読める。response.json() も非同期であることに注意しよう。ボディはストリームとして届くため、パースにも await が必要だ。

fetch の落とし穴:エラーは自分で判定する

fetch には重要な仕様上の特性があります。ネットワークエラー以外では Promise が reject されないという点です。

404 や 500 が返ってきても fetch は正常に resolve します。これは多くの開発者が最初に踏む罠です。

async function fetchData(url) {
  const response = await fetch(url);

  if (!response.ok) {
    throw new Error(`HTTP error: ${response.status}`);
  }

  return response.json();
}

response.okstatus が 200〜299 のときだけ true になります。このチェックを入れるのが fetch を使う際のイディオムです。XHR では xhr.status を手動でチェックするのが当然でしたが、fetch では Promise が resolve するため見落としやすいので注意が必要です。

タイムアウトと中断(AbortController)

fetch 単体にはタイムアウト機能がない。時間のかかるリクエストをキャンセルしたい場合は AbortController を使う。

async function fetchWithTimeout(url, ms = 5000) {
  const controller = new AbortController();
  const timerId = setTimeout(() => controller.abort(), ms);

  try {
    const response = await fetch(url, { signal: controller.signal });
    if (!response.ok) throw new Error(`HTTP error: ${response.status}`);
    return await response.json();
  } catch (err) {
    if (err.name === 'AbortError') {
      throw new Error('Request timed out');
    }
    throw err;
  } finally {
    clearTimeout(timerId);
  }
}

AbortController.abort() を呼ぶと、signal を渡したすべての fetch が即座に中断されます。ユーザー操作によるキャンセル(検索ボックスのデバウンス処理など)にも同じ仕組みが使えます。AbortError は catch で名前を確認して識別します。

XHR vs fetch ── どちらを使う?

XHR と fetch の比較

現代のフロントエンド開発では fetch を使えばほぼすべてのケースをカバーできます。読みやすく、Promise ネイティブで、モダンブラウザはもちろん Node.js 18 以降でもグローバルに使えます。

ただし、XHR が今も有利な場面が一つある。ファイルアップロードの進捗表示だ。

const xhr = new XMLHttpRequest();
xhr.upload.onprogress = function (event) {
  const percent = Math.round((event.loaded / event.total) * 100);
  console.log(`${percent}%`);
};
xhr.open('POST', '/upload');
xhr.send(formData);

fetch は 2025 年現在でも upload progress を取得する標準的な方法を持っていない(Streams API を使う実験的な手法はあるが、ブラウザサポートが限られる)。アップロードバーが必要な場面では XHR、または axios などのライブラリが現実的な選択肢になる。

まとめ

観点 XHR fetch
非同期モデル コールバック Promise / async-await
エラー判定 status を手動確認 response.ok を必ずチェック
タイムアウト xhr.timeout で指定可 AbortController が必要
upload progress xhr.upload.onprogress 標準手段なし
現代的な用途 限定的 ほぼすべてのケース

Ajax は今も Web の根幹にあります。XHR がその歴史的な実装であり、fetch がその現代的な答えです。response.ok の確認と AbortController によるキャンセル制御を習慣にするだけで、fetch を安全に使いこなせます。