基礎

JavaScriptのAjax入門|fetch APIでサーバーと非同期通信する方法

Ajax(Asynchronous JavaScript and XML)は、ページをリロードせずにサーバーとデータをやり取りする技術です。現代のWebアプリケーションでは、ユーザー操作に応じてAPIからデータを取得したり、フォームデータを送信したりする際に不可欠です。

かつては XMLHttpRequest が使われていましたが、現在は fetch APIが標準です。この記事では、fetch を使ったGET・POSTリクエスト、エラーハンドリング、async/awaitとの組み合わせを実践的に解説します。

基本的な使い方(GETリクエスト)

fetch() はPromiseを返す関数で、URLを指定するとHTTPリクエストを送信します。レスポンスの処理には .then() チェーンまたは async/await を使います。

JavaScript
// 基本的なGETリクエスト(Promiseチェーン)
fetch('https://jsonplaceholder.typicode.com/users/1')
  .then(response => {
    console.log('ステータス:', response.status);
    console.log('OK:', response.ok);
    return response.json();  // JSONとしてパース
  })
  .then(data => {
    console.log('ユーザー名:', data.name);
    console.log('メール:', data.email);
  })
  .catch(error => {
    console.log('エラー:', error.message);
  });
実行結果
ステータス: 200
OK: true
ユーザー名: Leanne Graham
メール: Sincere@april.biz

fetch() が返すPromiseは、レスポンスオブジェクトで解決されます。response.json() でJSONをパースし、response.text() でテキストとして取得します。response.ok はステータスコードが200-299の場合に true になります。

async/await で書く

async/await を使うと、非同期処理を同期的なコードのように読みやすく書けます。

JavaScript
// async/awaitを使ったGETリクエスト
async function getUser(id) {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users/' + id);

    if (!response.ok) {
      throw new Error('HTTP ' + response.status);
    }

    const user = await response.json();
    console.log('名前:', user.name);
    console.log('会社:', user.company.name);
    return user;
  } catch (error) {
    console.log('取得失敗:', error.message);
    return null;
  }
}

// 複数のリクエストを並行実行
async function getAllUsers() {
  try {
    const urls = [1, 2, 3].map(id =>
      fetch('https://jsonplaceholder.typicode.com/users/' + id)
    );
    const responses = await Promise.all(urls);
    const users = await Promise.all(responses.map(r => r.json()));

    users.forEach(user => {
      console.log(user.id + ': ' + user.name);
    });
  } catch (error) {
    console.log('エラー:', error.message);
  }
}

getUser(1);
getAllUsers();
実行結果
名前: Leanne Graham
会社: Romaguera-Crona
1: Leanne Graham
2: Ervin Howell
3: Clementine Bauch

Promise.all() を使うと複数のリクエストを並行して実行し、すべて完了するのを待てます。逐次実行に比べて大幅に高速です。ただし、1つでも失敗すると全体がエラーになるため、部分的な成功を許容したい場合は Promise.allSettled() を使います。

POSTリクエスト

データをサーバーに送信するには、fetch() の第2引数にメソッドやボディを指定します。

JavaScript
// JSON形式のPOSTリクエスト
async function createPost(title, body) {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      title: title,
      body: body,
      userId: 1
    })
  });

  const data = await response.json();
  console.log('作成されたID:', data.id);
  console.log('タイトル:', data.title);
  return data;
}

createPost('テスト記事', 'これはテスト本文です');

// フォームデータの送信
async function submitForm(formElement) {
  const formData = new FormData(formElement);

  const response = await fetch('/api/submit', {
    method: 'POST',
    body: formData  // Content-Typeは自動設定される
  });

  return await response.json();
}
実行結果
作成されたID: 101
タイトル: テスト記事

JSON送信では Content-Type: application/json ヘッダーの指定と JSON.stringify() が必要です。FormData を使う場合は Content-Type を手動設定しないでください。ブラウザが自動的に multipart/form-data とboundaryを設定します。

実践例:ローディング表示付きのデータ取得

JavaScript
async function loadUserList() {
  const container = document.querySelector('#user-list');
  const loadingEl = document.querySelector('#loading');

  // ローディング表示
  loadingEl.style.display = 'block';
  container.innerHTML = '';

  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users');

    if (!response.ok) {
      throw new Error('データの取得に失敗しました');
    }

    const users = await response.json();

    // ユーザー一覧を表示
    const html = users.map(user =>
      '
' + '

' + user.name + '

' + '

' + user.email + '

' + '
' ).join(''); container.innerHTML = html; console.log(users.length + '件のユーザーを表示'); } catch (error) { container.innerHTML = '

エラー: ' + error.message + '

'; console.log('エラー:', error.message); } finally { // 成功・失敗に関わらずローディングを非表示 loadingEl.style.display = 'none'; } }

try...catch...finally パターンは、ローディング表示の制御に最適です。finally ブロックは成功時も失敗時も必ず実行されるため、ローディング表示の非表示忘れを防げます。

fetch は404でもrejectしない

fetch() はネットワークエラー(接続不可)の場合のみPromiseがrejectされます。404や500などのHTTPエラーではrejectされず、response.okfalse になるだけです。そのため、if (!response.ok) のチェックが重要です。

CORS(Cross-Origin Resource Sharing)に注意

異なるドメインのAPIにリクエストする場合、サーバー側でCORSヘッダーが設定されていないとブラウザがリクエストをブロックします。「CORS policy」エラーが出た場合は、サーバー側で Access-Control-Allow-Origin ヘッダーを設定するか、プロキシサーバーを経由する必要があります。

まとめ

  • fetch() はPromiseベースのHTTPリクエストAPIで、Ajax通信の標準手段
  • response.json() でJSONパース、response.ok でステータスの成否を確認する
  • async/awaittry...catch で読みやすいエラーハンドリングができる
  • Promise.all() で複数リクエストを並行実行し、高速化できる
  • fetch は404でもrejectしないため、response.ok のチェックが必須