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.ok は status が 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 ── どちらを使う?
現代のフロントエンド開発では 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 を安全に使いこなせます。