JavaScript fetchが動かない?POST/GETリクエスト・CORS・戻り値エラーの原因と対処法を完全ガイド

js-fetch-error javascript
記事内に広告が含まれています。

「JavaScriptのfetchが使えない…」そんなお悩み、ありませんか?

モダンなフロントエンド開発において、fetchはAPI通信に欠かせない存在です。ですが、「なぜか動かない」「エラーが出る」「何も返ってこない」といったトラブルに直面して、つまずいてしまう方も多いのではないでしょうか。とくに初心者の方や、Ajaxとの違いになじみのない方にとっては、原因が見えにくく不安になるものです。

本記事では、「JavaScript fetch 使えない」という検索ニーズに応えるべく、考えられる原因とその解決策を、構文の基本から非同期処理の注意点まで網羅的に解説します。さらに、よくあるエラーやCORS問題、POSTリクエストが送れないケースなど、実践でつまずきやすいポイントにも焦点を当て、コピペOKな実用コード付きでわかりやすく解説しています。

「fetchが動かない」状態を解消し、快適にAPI通信を行えるようになるために、ぜひ最後までご覧ください。

この記事を読んでわかること

  • fetchの基本構文とGET/POSTリクエストの正しい使い方
  • 「Failed to fetch」やCORSエラーなど、fetchが使えない時の原因と対処法
  • リクエストの種類別(POST/GET)のトラブルシューティング
  • thenasync/awaitを用いた非同期処理の正しい書き方
  • fetchでよくある質問とその明確な回答
  • 開発者ツールを活用したデバッグ方法や注意点
fetchでCORSエラーが出た時の対処法|no-corsやAccess-Control-Allow-Originを解説
fetchで困るCORSエラーを解説!「なぜ?」の仕組み、ブラウザ開発者ツールでのデバッグ、サーバー/クライアントでの具体的解決策(Access-Control-Allow-Origin設定など)、よくある失敗例、ローカル環境対策まで網羅。この記事でCORSエラーを理解し、自信を持って対処できるようになりましょう。

fetchが「使えない」時の主な原因と解決策

JavaScript開発において、fetch APIは現代的なHTTPリクエストを行うための標準的な方法として広く使われています。しかし、「javascript fetch 使えない」と検索される方が多いのも事実です。

fetchが使えない問題は、主に以下の3つのカテゴリに分類できます:

  • 基本的な構文の理解不足
  • ネットワークエラー
  • CORS(Cross-Origin Resource Sharing)の制限

それぞれについて、実践的なコード例とともに解決方法をご紹介します。

fetchの基本構文と使い方(GET/POSTリクエストの違い)

まず、fetchが「使えない」と感じる最も一般的な原因は、基本的な構文の理解不足です。fetchは従来のXMLHttpRequestと異なる書き方をするため、正しい構文を理解することが重要です。

GET リクエストの基本構文

// 基本的なGETリクエスト
fetch('<https://api.example.com/data>')
  .then(response => {
    // レスポンスが成功したかチェック
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json(); // JSONとして解析
  })
  .then(data => {
    console.log('取得したデータ:', data);
  })
  .catch(error => {
    console.error('エラーが発生しました:', error);
  });

POST リクエストの基本構文

// JSONデータを送信するPOSTリクエスト
const postData = {
  name: '田中太郎',
  email: 'tanaka@example.com'
};

fetch('<https://api.example.com/users>', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(postData) // オブジェクトをJSON文字列に変換
})
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => {
    console.log('送信成功:', data);
  })
  .catch(error => {
    console.error('送信エラー:', error);
  });

重要なポイント:

  • fetchはPromiseを返すため、.then()チェーンまたはasync/awaitで処理する必要があります
  • response.okでHTTPステータスが成功(200-299)かどうかを確認することが重要です
  • POSTリクエストではmethodheadersbodyオプションを適切に設定する必要があります

fetchで「Failed to fetch」エラーが出る主な原因と対処法

「Failed to fetch」エラーは、fetchが最も一般的に遭遇するエラーの一つです。このエラーが発生する主な原因と対処法を詳しく見ていきましょう。

原因1:ネットワーク接続の問題

// ネットワークエラーを適切にハンドリングする方法
async function fetchWithNetworkCheck(url) {
  try {
    // ネットワーク状態をチェック
    if (!navigator.onLine) {
      throw new Error('インターネット接続がありません');
    }

    const response = await fetch(url);

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

    return await response.json();
  } catch (error) {
    if (error.message === 'Failed to fetch') {
      console.error('ネットワークエラー: サーバーに接続できません');
      // ユーザーに分かりやすいエラーメッセージを表示
      throw new Error('サーバーに接続できません。インターネット接続を確認してください。');
    }
    throw error;
  }
}

原因2:不正なURL

// URLの検証を行う関数
function isValidUrl(string) {
  try {
    new URL(string);
    return true;
  } catch (_) {
    return false;
  }
}

// 安全なfetch関数
async function safeFetch(url) {
  // URLの妥当性をチェック
  if (!isValidUrl(url)) {
    throw new Error('無効なURLです: ' + url);
  }

  try {
    const response = await fetch(url);
    return response;
  } catch (error) {
    console.error('Fetch エラー:', error.message);
    throw error;
  }
}

// 使用例
safeFetch('<https://api.example.com/data>')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('エラー:', error.message));

原因3:タイムアウトの設定

fetchにはデフォルトのタイムアウト機能がないため、長時間応答がない場合に対処する必要があります。

// タイムアウト機能付きのfetch関数
function fetchWithTimeout(url, options = {}, timeout = 5000) {
  return Promise.race([
    fetch(url, options),
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error('リクエストがタイムアウトしました')), timeout)
    )
  ]);
}

// 使用例
fetchWithTimeout('<https://api.example.com/slow-endpoint>', {}, 3000)
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => console.log('データ取得成功:', data))
  .catch(error => {
    if (error.message === 'リクエストがタイムアウトしました') {
      console.error('サーバーの応答が遅すぎます');
    } else {
      console.error('エラー:', error.message);
    }
  });

CORS(クロスオリジン)エラーの発生理由と解決方法【Access-Control-Allow-Origin】

CORS(Cross-Origin Resource Sharing)エラーは、fetchを使用する際に最も頻繁に遭遇する問題の一つです。このエラーは、ブラウザのセキュリティ機能により、異なるオリジン(ドメイン、プロトコル、ポート)間でのリソース共有が制限されることで発生します。

CORSエラーの典型的な症状

// このコードはCORSエラーを引き起こす可能性があります
fetch('<https://api.external-service.com/data>')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => {
    // "Access to fetch at '<https://api.external-service.com/data>'
    // from origin '<http://localhost:3000>' has been blocked by CORS policy"
    console.error('CORS エラー:', error);
  });

解決方法1:サーバー側でCORSヘッダーを設定

サーバー側で適切なCORSヘッダーを設定することが根本的な解決策です。

// Node.js + Express での CORS 設定例(参考)
/*
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*'); // または特定のドメイン
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
  next();
});
*/

// フロントエンド側:プリフライトリクエストを考慮したfetch
async function corsAwareFetch(url, data = null) {
  const options = {
    method: data ? 'POST' : 'GET',
    headers: {
      'Content-Type': 'application/json',
      // 必要に応じて認証ヘッダーなどを追加
    }
  };

  if (data) {
    options.body = JSON.stringify(data);
  }

  try {
    const response = await fetch(url, options);

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

    return await response.json();
  } catch (error) {
    if (error.message.includes('CORS')) {
      console.error('CORS エラー: サーバー側でCORS設定を確認してください');
      throw new Error('サーバーとの通信でCORSエラーが発生しました');
    }
    throw error;
  }
}

解決方法2:プロキシサーバーを使用

開発環境では、プロキシサーバーを使用してCORS制限を回避できます。

// 開発環境用:プロキシ経由でAPIを呼び出す
const API_BASE_URL = process.env.NODE_ENV === 'production'
  ? '<https://api.example.com>'
  : '/api'; // 開発時はプロキシ経由

async function apiCall(endpoint, options = {}) {
  const url = `${API_BASE_URL}${endpoint}`;

  try {
    const response = await fetch(url, {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        ...options.headers
      }
    });

    if (!response.ok) {
      const errorData = await response.text();
      throw new Error(`API Error: ${response.status} - ${errorData}`);
    }

    return await response.json();
  } catch (error) {
    console.error('API呼び出しエラー:', error);
    throw error;
  }
}

// 使用例
apiCall('/users', { method: 'GET' })
  .then(users => console.log('ユーザー一覧:', users))
  .catch(error => console.error('エラー:', error.message));

解決方法3:JSONPの代替手段(限定的な場合)

古いAPIでJSONPをサポートしている場合の代替手段:

// JSONP形式のリクエスト(GETのみ、限定的な用途)
function jsonpRequest(url, callbackName = 'callback') {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    const timeoutId = setTimeout(() => {
      cleanup();
      reject(new Error('JSONP request timeout'));
    }, 10000);

    const cleanup = () => {
      if (script.parentNode) {
        script.parentNode.removeChild(script);
      }
      clearTimeout(timeoutId);
      delete window[callbackName];
    };

    window[callbackName] = (data) => {
      cleanup();
      resolve(data);
    };

    script.onerror = () => {
      cleanup();
      reject(new Error('JSONP request failed'));
    };

    script.src = `${url}?callback=${callbackName}`;
    document.head.appendChild(script);
  });
}

// 使用例(JSONPをサポートするAPIの場合のみ)
jsonpRequest('<https://api.example.com/data>')
  .then(data => console.log('JSONP データ:', data))
  .catch(error => console.error('JSONP エラー:', error));

fetchリクエストの種類別トラブルシューティング完全ガイド

fetchAPIを実際の開発で使用する際、リクエストの種類によって異なる問題が発生することがあります。このセクションでは、POSTGET、そしてレスポンスの解析における具体的なトラブルシューティング方法を詳しく解説します。「javascript fetch 使えない」という問題の多くは、リクエストの種類に応じた適切な処理方法を理解することで解決できます。

fetch POSTリクエストでデータが送れない? 失敗しない送信方法とデバッグ術

POSTリクエストでデータが正常に送信されない問題は、フォームデータの処理やAPI連携において特に頻繁に発生します。以下に、よくある問題と解決策を詳しく説明します。

問題1:Content-Typeヘッダーの設定ミス

最も一般的な問題の一つが、Content-Typeヘッダーの不適切な設定です。送信するデータの形式に応じて、正しいContent-Typeを指定する必要があります。

// ❌ 間違った例:Content-Typeが設定されていない
fetch('/api/users', {
  method: 'POST',
  body: JSON.stringify({ name: 'John', email: 'john@example.com' })
})
.then(response => response.json())
.catch(error => console.error('エラー:', error));

// ✅ 正しい例:JSONデータの送信
async function sendJsonData(userData) {
  try {
    const response = await fetch('/api/users', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json', // 重要:JSONの場合は必須
        'Accept': 'application/json'
      },
      body: JSON.stringify(userData)
    });

    if (!response.ok) {
      // レスポンスのエラー詳細を取得
      const errorText = await response.text();
      throw new Error(`HTTP ${response.status}: ${errorText}`);
    }

    return await response.json();
  } catch (error) {
    console.error('JSON送信エラー:', error);
    throw error;
  }
}

// 使用例
const newUser = { name: '山田太郎', email: 'yamada@example.com', age: 30 };
sendJsonData(newUser)
  .then(result => console.log('ユーザー作成成功:', result))
  .catch(error => console.error('送信失敗:', error.message));

問題2:フォームデータの送信方法

HTMLフォームデータを送信する場合は、FormDataオブジェクトを使用します。

// フォームデータの送信(ファイルアップロード含む)
async function sendFormData(formElement) {
  const formData = new FormData(formElement);

  // 追加のデータをFormDataに追加
  formData.append('timestamp', new Date().toISOString());
  formData.append('userId', '12345');

  try {
    const response = await fetch('/api/upload', {
      method: 'POST',
      // 注意:FormDataを使用する場合、Content-Typeを設定しない
      // ブラウザが自動的に適切なboundaryを含むContent-Typeを設定します
      body: formData
    });

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(`アップロード失敗: ${errorData.message}`);
    }

    return await response.json();
  } catch (error) {
    console.error('フォームデータ送信エラー:', error);
    throw error;
  }
}

// HTMLフォームからの送信
document.getElementById('uploadForm').addEventListener('submit', async (e) => {
  e.preventDefault();

  try {
    const result = await sendFormData(e.target);
    console.log('アップロード成功:', result);
    // 成功時の処理
    alert('ファイルのアップロードが完了しました');
  } catch (error) {
    console.error('アップロードエラー:', error);
    alert('アップロードに失敗しました: ' + error.message);
  }
});

問題3:URL-encoded形式のデータ送信

従来のHTMLフォーム送信と同様の形式でデータを送信する場合:

// URL-encoded形式でのデータ送信
async function sendUrlEncodedData(data) {
  // オブジェクトをURL-encoded文字列に変換
  const formBody = Object.keys(data)
    .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
    .join('&');

  try {
    const response = await fetch('/api/form-submit', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: formBody
    });

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

    return await response.json();
  } catch (error) {
    console.error('URL-encoded送信エラー:', error);
    throw error;
  }
}

// 使用例
const formData = {
  username: 'user123',
  password: 'securePass',
  email: 'user@example.com'
};

sendUrlEncodedData(formData)
  .then(result => console.log('送信成功:', result))
  .catch(error => console.error('送信失敗:', error));

デバッグ用のPOSTリクエスト監視関数

// デバッグ情報付きのPOSTリクエスト関数
async function debugPost(url, data, options = {}) {
  const requestInfo = {
    url,
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      ...options.headers
    },
    body: JSON.stringify(data)
  };

  console.log('🚀 POSTリクエスト送信:', requestInfo);

  try {
    const startTime = Date.now();
    const response = await fetch(url, requestInfo);
    const endTime = Date.now();

    console.log(`⏱️ リクエスト時間: ${endTime - startTime}ms`);
    console.log('📨 レスポンス:', {
      status: response.status,
      statusText: response.statusText,
      headers: Object.fromEntries(response.headers.entries())
    });

    if (!response.ok) {
      const errorText = await response.text();
      console.error('❌ エラーレスポンス:', errorText);
      throw new Error(`HTTP ${response.status}: ${errorText}`);
    }

    const result = await response.json();
    console.log('✅ 成功レスポンス:', result);
    return result;

  } catch (error) {
    console.error('🚨 POSTリクエストエラー:', error);
    throw error;
  }
}

fetch GETリクエストで何も返ってこない? 期待通りのデータを取得する手順

GETリクエストで期待通りのデータが取得できない問題は、URLパラメータの処理、キャッシュの問題、またはレスポンスの解析方法に関連していることが多いです。

問題1:URLパラメータの正しい構築

// URLパラメータを安全に構築する関数
function buildUrlWithParams(baseUrl, params) {
  const url = new URL(baseUrl);

  // パラメータを追加
  Object.keys(params).forEach(key => {
    if (params[key] !== null && params[key] !== undefined) {
      url.searchParams.append(key, params[key]);
    }
  });

  return url.toString();
}

// 検索APIの呼び出し例
async function searchUsers(searchParams) {
  const baseUrl = '<https://api.example.com/users>';
  const params = {
    q: searchParams.query,
    page: searchParams.page || 1,
    limit: searchParams.limit || 10,
    sort: searchParams.sort || 'created_at',
    order: searchParams.order || 'desc'
  };

  const url = buildUrlWithParams(baseUrl, params);
  console.log('🔍 検索URL:', url);

  try {
    const response = await fetch(url, {
      method: 'GET',
      headers: {
        'Accept': 'application/json',
        'User-Agent': 'MyApp/1.0'
      }
    });

    if (!response.ok) {
      throw new Error(`検索失敗: HTTP ${response.status}`);
    }

    const data = await response.json();

    // データ検証
    if (!data || !Array.isArray(data.users)) {
      throw new Error('期待されるデータ形式ではありません');
    }

    return data;
  } catch (error) {
    console.error('検索エラー:', error);
    throw error;
  }
}

// 使用例
const searchConditions = {
  query: 'JavaScript',
  page: 1,
  limit: 20
};

searchUsers(searchConditions)
  .then(results => {
    console.log(`検索結果: ${results.users.length}件`);
    console.log('ユーザー:', results.users);
  })
  .catch(error => console.error('検索失敗:', error));

問題2:キャッシュ制御とデータの鮮度管理

// キャッシュ制御機能付きのGETリクエスト
async function fetchWithCacheControl(url, options = {}) {
  const {
    useCache = true,
    cacheTime = 5 * 60 * 1000, // 5分
    forceRefresh = false
  } = options;

  const cacheKey = `fetch_cache_${url}`;

  // キャッシュから取得を試行
  if (useCache && !forceRefresh) {
    const cached = localStorage.getItem(cacheKey);
    if (cached) {
      const { data, timestamp } = JSON.parse(cached);
      const now = Date.now();

      if (now - timestamp < cacheTime) {
        console.log('📦 キャッシュからデータを取得:', url);
        return data;
      }
    }
  }

  try {
    const fetchOptions = {
      method: 'GET',
      headers: {
        'Cache-Control': forceRefresh ? 'no-cache' : 'max-age=300',
        'Accept': 'application/json',
        ...options.headers
      }
    };

    console.log('🌐 サーバーからデータを取得:', url);
    const response = await fetch(url, fetchOptions);

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

    const data = await response.json();

    // キャッシュに保存
    if (useCache) {
      localStorage.setItem(cacheKey, JSON.stringify({
        data,
        timestamp: Date.now()
      }));
    }

    return data;
  } catch (error) {
    console.error('データ取得エラー:', error);
    throw error;
  }
}

// 使用例
async function loadUserProfile(userId) {
  try {
    const profile = await fetchWithCacheControl(
      `/api/users/${userId}`,
      { cacheTime: 10 * 60 * 1000 } // 10分キャッシュ
    );

    console.log('ユーザープロフィール:', profile);
    return profile;
  } catch (error) {
    console.error('プロフィール読み込みエラー:', error);
    throw error;
  }
}

問題3:条件付きリクエストとデータ同期

// ETagを使用した条件付きリクエスト
class DataManager {
  constructor() {
    this.cache = new Map();
    this.etags = new Map();
  }

  async fetchWithETag(url, options = {}) {
    const etag = this.etags.get(url);
    const headers = {
      'Accept': 'application/json',
      ...options.headers
    };

    // ETagがある場合は条件付きリクエスト
    if (etag) {
      headers['If-None-Match'] = etag;
    }

    try {
      const response = await fetch(url, {
        method: 'GET',
        headers
      });

      if (response.status === 304) {
        // データが変更されていない場合
        console.log('💾 データは最新です(304 Not Modified)');
        return this.cache.get(url);
      }

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

      const data = await response.json();

      // ETagとデータをキャッシュ
      const newETag = response.headers.get('ETag');
      if (newETag) {
        this.etags.set(url, newETag);
        this.cache.set(url, data);
      }

      return data;
    } catch (error) {
      console.error('条件付きリクエストエラー:', error);
      throw error;
    }
  }
}

// 使用例
const dataManager = new DataManager();

async function loadArticles() {
  try {
    const articles = await dataManager.fetchWithETag('/api/articles');
    console.log('記事一覧:', articles);
    return articles;
  } catch (error) {
    console.error('記事読み込みエラー:', error);
  }
}

fetchの戻り値が謎…JSON/テキストの正しいパースと変換エラー回避術

fetchの戻り値の処理で最も混乱しやすいのが、Responseオブジェクトの適切な解析方法です。レスポンスの形式に応じた正しい処理を行うことで、多くの問題を回避できます。

問題1:レスポンス形式の自動判定

// レスポンス形式を自動判定して適切に処理する関数
async function parseResponse(response) {
  // Content-Typeヘッダーを確認
  const contentType = response.headers.get('Content-Type') || '';

  console.log('📄 Content-Type:', contentType);

  try {
    if (contentType.includes('application/json')) {
      const data = await response.json();
      console.log('📋 JSONデータ:', data);
      return data;
    } else if (contentType.includes('text/html')) {
      const html = await response.text();
      console.log('🌐 HTMLデータ:', html.substring(0, 100) + '...');
      return html;
    } else if (contentType.includes('text/plain')) {
      const text = await response.text();
      console.log('📝 テキストデータ:', text);
      return text;
    } else if (contentType.includes('application/xml') || contentType.includes('text/xml')) {
      const xmlText = await response.text();
      const parser = new DOMParser();
      const xmlDoc = parser.parseFromString(xmlText, 'text/xml');
      console.log('📄 XMLデータ:', xmlDoc);
      return xmlDoc;
    } else if (contentType.startsWith('image/')) {
      const blob = await response.blob();
      console.log('🖼️ 画像データ:', blob);
      return blob;
    } else {
      // 不明な形式の場合はArrayBufferとして取得
      const buffer = await response.arrayBuffer();
      console.log('📦 バイナリデータ:', buffer);
      return buffer;
    }
  } catch (error) {
    console.error('レスポンス解析エラー:', error);
    throw new Error(`レスポンスの解析に失敗しました: ${error.message}`);
  }
}

// 安全なレスポンス処理を行うfetch関数
async function safeFetch(url, options = {}) {
  try {
    console.log('🚀 リクエスト送信:', url);
    const response = await fetch(url, options);

    if (!response.ok) {
      // エラーレスポンスも適切に解析
      let errorMessage;
      try {
        const errorData = await parseResponse(response.clone());
        errorMessage = typeof errorData === 'object' ? errorData.message : errorData;
      } catch {
        errorMessage = response.statusText;
      }
      throw new Error(`HTTP ${response.status}: ${errorMessage}`);
    }

    return await parseResponse(response);
  } catch (error) {
    console.error('リクエストエラー:', error);
    throw error;
  }
}

問題2:JSONパースエラーの対処

// JSONパースエラーを適切にハンドリングする関数
async function safeJsonParse(response) {
  const text = await response.text();

  // 空のレスポンスをチェック
  if (!text || text.trim() === '') {
    console.warn('⚠️ 空のレスポンスです');
    return null;
  }

  try {
    const data = JSON.parse(text);
    return data;
  } catch (error) {
    console.error('❌ JSON解析エラー:', error);
    console.error('📄 生レスポンス:', text);

    // JSONでない場合は文字列として返す
    if (text.startsWith('<html')) {
      throw new Error('JSONを期待しましたが、HTMLが返されました。APIエンドポイントを確認してください。');
    } else if (text.includes('<!DOCTYPE')) {
      throw new Error('JSONを期待しましたが、HTMLドキュメントが返されました。');
    } else {
      throw new Error(`JSONの解析に失敗しました: ${error.message}`);
    }
  }
}

// 使用例
async function fetchUserData(userId) {
  try {
    const response = await fetch(`/api/users/${userId}`);

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

    const userData = await safeJsonParse(response);

    // データ検証
    if (!userData || typeof userData !== 'object') {
      throw new Error('無効なユーザーデータです');
    }

    console.log('✅ ユーザーデータ取得成功:', userData);
    return userData;
  } catch (error) {
    console.error('ユーザーデータ取得エラー:', error);
    throw error;
  }
}

問題3:ストリーミング処理とプログレス表示

// ストリーミング処理でプログレス表示を行う関数
async function fetchWithProgress(url, options = {}) {
  const response = await fetch(url, options);

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

  const contentLength = response.headers.get('Content-Length');
  const total = contentLength ? parseInt(contentLength, 10) : 0;

  if (!total) {
    console.warn('⚠️ Content-Lengthが不明です。プログレス表示はできません。');
    return await response.text();
  }

  const reader = response.body.getReader();
  const chunks = [];
  let received = 0;

  try {
    while (true) {
      const { done, value } = await reader.read();

      if (done) break;

      chunks.push(value);
      received += value.length;

      // プログレス表示
      const progress = (received / total) * 100;
      console.log(`📥 ダウンロード進行状況: ${progress.toFixed(1)}% (${received}/${total} bytes)`);

      // プログレスバーを更新(DOM要素がある場合)
      const progressBar = document.getElementById('downloadProgress');
      if (progressBar) {
        progressBar.style.width = `${progress}%`;
        progressBar.textContent = `${progress.toFixed(1)}%`;
      }
    }

    // チャンクを結合
    const allChunks = new Uint8Array(received);
    let position = 0;
    for (const chunk of chunks) {
      allChunks.set(chunk, position);
      position += chunk.length;
    }

    return new TextDecoder().decode(allChunks);
  } finally {
    reader.releaseLock();
  }
}

// 使用例
async function downloadLargeFile(url) {
  try {
    console.log('📥 ダウンロード開始:', url);
    const data = await fetchWithProgress(url);
    console.log('✅ ダウンロード完了');
    return data;
  } catch (error) {
    console.error('ダウンロードエラー:', error);
    throw error;
  }
}

非同期処理とfetchの連携:エラーなしで動かすテクニック

JavaScriptのfetchは非同期処理を前提とした設計になっており、適切に扱わないと期待通りの動作をしない場合があります。多くの開発者が「fetchが使えない」と感じる原因の一つが、この非同期処理の理解不足にあります。ここでは、非同期処理とfetchを連携させる際の代表的な問題と、その解決策について具体的なコード例とともに解説します。

fetchのthenチェーンが繋がらない? 非同期処理の実行順序を制御する方法

thenチェーンの基本的な書き方

fetchはPromiseを返すため、.then()メソッドを使って非同期処理を順次実行できます。しかし、正しい書き方を理解していないと、処理が期待通りに繋がらない場合があります。

// ❌ 間違った書き方:thenチェーンが正しく繋がらない例
fetch('<https://api.example.com/users>')
  .then(response => {
    console.log('レスポンス取得完了');
    response.json(); // returnを忘れている!
  })
  .then(data => {
    console.log(data); // undefinedが出力される
  });

// ✅ 正しい書き方:returnを忘れずに
fetch('<https://api.example.com/users>')
  .then(response => {
    console.log('レスポンス取得完了');
    return response.json(); // returnが重要!
  })
  .then(data => {
    console.log(data); // 正しくデータが表示される
    return data.filter(user => user.active); // 次の処理に渡す
  })
  .then(activeUsers => {
    console.log('アクティブユーザー:', activeUsers);
  })
  .catch(error => {
    console.error('エラーが発生しました:', error);
  });

複数のfetchを順番に実行する方法

複数のAPIを順番に呼び出す必要がある場合、thenチェーンを使って順次実行できます:

// ユーザー情報を取得してから、そのユーザーの投稿を取得する例
fetch('<https://api.example.com/user/123>')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  })
  .then(user => {
    console.log('ユーザー情報:', user);
    // ユーザー情報を取得後、そのユーザーの投稿を取得
    return fetch(`https://api.example.com/users/${user.id}/posts`);
  })
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  })
  .then(posts => {
    console.log('投稿一覧:', posts);
  })
  .catch(error => {
    console.error('処理中にエラーが発生:', error);
  });

並列処理でパフォーマンス向上

関連性のない複数のAPIを同時に呼び出すことで、処理時間を短縮できます:

// Promise.allを使用した並列処理
Promise.all([
  fetch('<https://api.example.com/users>'),
  fetch('<https://api.example.com/posts>'),
  fetch('<https://api.example.com/comments>')
])
.then(responses => {
  // すべてのレスポンスが正常かチェック
  responses.forEach(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
  });

  // すべてのレスポンスをJSONに変換
  return Promise.all(responses.map(response => response.json()));
})
.then(([users, posts, comments]) => {
  console.log('ユーザー:', users);
  console.log('投稿:', posts);
  console.log('コメント:', comments);
})
.catch(error => {
  console.error('並列処理でエラー:', error);
});

async/awaitでfetchをもっとシンプルに! エラーハンドリングも完璧にこなす

async/awaitの基本的な使い方

async/awaitを使用すると、非同期処理をより直感的に書くことができます:

// async/awaitを使用したfetchの基本形
async function fetchUserData() {
  try {
    // APIからユーザーデータを取得
    const response = await fetch('<https://api.example.com/users/123>');

    // レスポンスが正常かチェック
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    // JSONデータに変換
    const userData = await response.json();

    console.log('ユーザーデータ:', userData);
    return userData;

  } catch (error) {
    console.error('データ取得エラー:', error);
    throw error; // 呼び出し元にエラーを再スロー
  }
}

// 関数の使用例
fetchUserData()
  .then(data => {
    console.log('取得完了:', data);
  })
  .catch(error => {
    console.error('最終的なエラー処理:', error);
  });

複数のfetchを順次実行(async/await版)

async function fetchUserAndPosts(userId) {
  try {
    // 1. ユーザー情報を取得
    console.log('ユーザー情報を取得中...');
    const userResponse = await fetch(`https://api.example.com/users/${userId}`);

    if (!userResponse.ok) {
      throw new Error(`ユーザー取得エラー: ${userResponse.status}`);
    }

    const user = await userResponse.json();
    console.log('ユーザー情報取得完了:', user.name);

    // 2. そのユーザーの投稿を取得
    console.log('投稿を取得中...');
    const postsResponse = await fetch(`https://api.example.com/users/${userId}/posts`);

    if (!postsResponse.ok) {
      throw new Error(`投稿取得エラー: ${postsResponse.status}`);
    }

    const posts = await postsResponse.json();
    console.log(`投稿取得完了: ${posts.length}件`);

    // 3. 結果をまとめて返す
    return {
      user: user,
      posts: posts,
      summary: `${user.name}さんの投稿${posts.length}件を取得しました`
    };

  } catch (error) {
    console.error('fetchUserAndPosts エラー:', error);
    throw error;
  }
}

// 使用例
async function main() {
  try {
    const result = await fetchUserAndPosts(123);
    console.log(result.summary);
  } catch (error) {
    console.error('メイン処理でエラー:', error);
  }
}

main();

並列処理のasync/await版

async function fetchAllData() {
  try {
    console.log('複数のAPIを並列取得開始...');

    // 複数のfetchを同時に開始
    const [usersResponse, postsResponse, commentsResponse] = await Promise.all([
      fetch('<https://api.example.com/users>'),
      fetch('<https://api.example.com/posts>'),
      fetch('<https://api.example.com/comments>')
    ]);

    // すべてのレスポンスをチェック
    if (!usersResponse.ok) throw new Error(`Users API error: ${usersResponse.status}`);
    if (!postsResponse.ok) throw new Error(`Posts API error: ${postsResponse.status}`);
    if (!commentsResponse.ok) throw new Error(`Comments API error: ${commentsResponse.status}`);

    // JSONへの変換も並列で実行
    const [users, posts, comments] = await Promise.all([
      usersResponse.json(),
      postsResponse.json(),
      commentsResponse.json()
    ]);

    console.log('すべてのデータ取得完了');

    return {
      users: users,
      posts: posts,
      comments: comments,
      totalItems: users.length + posts.length + comments.length
    };

  } catch (error) {
    console.error('並列取得でエラー:', error);
    throw error;
  }
}

// 使用例
fetchAllData()
  .then(data => {
    console.log(`合計${data.totalItems}件のデータを取得`);
  })
  .catch(error => {
    console.error('データ取得失敗:', error);
  });

エラーハンドリングのベストプラクティス

async function robustFetch(url, options = {}) {
  // デフォルトのオプションを設定
  const defaultOptions = {
    timeout: 10000, // 10秒でタイムアウト
    retries: 3,     // 3回まで再試行
    ...options
  };

  for (let attempt = 1; attempt <= defaultOptions.retries; attempt++) {
    try {
      console.log(`${url} への接続試行 ${attempt}/${defaultOptions.retries}`);

      // タイムアウト付きでfetch実行
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), defaultOptions.timeout);

      const response = await fetch(url, {
        ...defaultOptions,
        signal: controller.signal
      });

      clearTimeout(timeoutId);

      // HTTPエラーをチェック
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      console.log(`${url} への接続成功`);
      return response;

    } catch (error) {
      console.warn(`試行 ${attempt} 失敗:`, error.message);

      // 最後の試行でもエラーの場合は例外を投げる
      if (attempt === defaultOptions.retries) {
        throw new Error(`${url} への接続に${defaultOptions.retries}回失敗しました: ${error.message}`);
      }

      // 次の試行まで少し待つ
      await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
    }
  }
}

// 使用例
async function safeApiCall() {
  try {
    const response = await robustFetch('<https://api.example.com/data>');
    const data = await response.json();
    console.log('データ取得成功:', data);
  } catch (error) {
    console.error('最終的にデータ取得に失敗:', error);
  }
}

fetchが遅い・タイムアウトする? パフォーマンス改善と高速化の秘訣

タイムアウト設定でレスポンス時間を制御

// AbortControllerを使用したタイムアウト設定
async function fetchWithTimeout(url, timeout = 5000) {
  const controller = new AbortController();

  // 指定時間後にリクエストを中止
  const timeoutId = setTimeout(() => {
    controller.abort();
  }, timeout);

  try {
    const response = await fetch(url, {
      signal: controller.signal,
      // キャッシュ戦略も指定
      cache: 'default'
    });

    clearTimeout(timeoutId);

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

    return response;

  } catch (error) {
    clearTimeout(timeoutId);

    if (error.name === 'AbortError') {
      throw new Error(`リクエストが${timeout}ms でタイムアウトしました`);
    }

    throw error;
  }
}

// 使用例
async function quickFetch() {
  try {
    const response = await fetchWithTimeout('<https://api.example.com/data>', 3000);
    const data = await response.json();
    console.log('高速取得成功:', data);
  } catch (error) {
    console.error('高速取得エラー:', error);
  }
}

キャッシュ戦略でパフォーマンス向上

// インメモリキャッシュを実装
class FetchCache {
  constructor(maxAge = 300000) { // デフォルト5分
    this.cache = new Map();
    this.maxAge = maxAge;
  }

  // キャッシュキーを生成
  generateKey(url, options) {
    return `${url}_${JSON.stringify(options || {})}`;
  }

  // キャッシュからデータを取得
  get(url, options) {
    const key = this.generateKey(url, options);
    const cached = this.cache.get(key);

    if (cached && (Date.now() - cached.timestamp) < this.maxAge) {
      console.log('キャッシュからデータを取得:', url);
      return cached.data;
    }

    return null;
  }

  // データをキャッシュに保存
  set(url, options, data) {
    const key = this.generateKey(url, options);
    this.cache.set(key, {
      data: data,
      timestamp: Date.now()
    });
  }

  // キャッシュをクリア
  clear() {
    this.cache.clear();
  }
}

// キャッシュ付きfetch関数
const fetchCache = new FetchCache(300000); // 5分間キャッシュ

async function cachedFetch(url, options = {}) {
  // キャッシュをチェック
  const cached = fetchCache.get(url, options);
  if (cached) {
    return cached;
  }

  try {
    console.log('新規にデータを取得:', url);
    const response = await fetch(url, options);

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

    const data = await response.json();

    // キャッシュに保存
    fetchCache.set(url, options, data);

    return data;

  } catch (error) {
    console.error('cachedFetch エラー:', error);
    throw error;
  }
}

// 使用例
async function demonstrateCache() {
  try {
    // 1回目:APIから取得
    console.log('--- 1回目の取得 ---');
    const data1 = await cachedFetch('<https://api.example.com/users>');
    console.log('データ1:', data1);

    // 2回目:キャッシュから取得
    console.log('--- 2回目の取得(キャッシュ使用) ---');
    const data2 = await cachedFetch('<https://api.example.com/users>');
    console.log('データ2:', data2);

  } catch (error) {
    console.error('キャッシュデモエラー:', error);
  }
}

大量データの効率的な処理

// ストリーミング処理で大量データを効率的に扱う
async function processLargeDataStream(url) {
  try {
    const response = await fetch(url);

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

    // ReadableStreamを取得
    const reader = response.body.getReader();
    const decoder = new TextDecoder();

    let buffer = '';
    let processedCount = 0;

    while (true) {
      const { done, value } = await reader.read();

      if (done) break;

      // チャンクを文字列に変換
      buffer += decoder.decode(value, { stream: true });

      // 行単位で処理
      const lines = buffer.split('\\n');
      buffer = lines.pop(); // 最後の不完全な行を保持

      for (const line of lines) {
        if (line.trim()) {
          try {
            const data = JSON.parse(line);
            console.log(`処理中: ${++processedCount}件目`, data);

            // ここで各データを処理
            await processDataItem(data);

          } catch (parseError) {
            console.warn('JSON解析エラー:', line);
          }
        }
      }
    }

    // 残りのバッファを処理
    if (buffer.trim()) {
      try {
        const data = JSON.parse(buffer);
        await processDataItem(data);
        processedCount++;
      } catch (parseError) {
        console.warn('最終JSON解析エラー:', buffer);
      }
    }

    console.log(`ストリーミング処理完了: ${processedCount}件`);

  } catch (error) {
    console.error('ストリーミング処理エラー:', error);
    throw error;
  }
}

async function processDataItem(data) {
  // 個別データの処理ロジック
  // 必要に応じて非同期処理を実装
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('データ処理完了:', data.id);
      resolve();
    }, 10);
  });
}

これらのテクニックを組み合わせることで、JavaScriptのfetchを使った非同期処理を効率的かつ安全に実装できます。特に、適切なエラーハンドリングとパフォーマンス最適化は、本番環境でのアプリケーション品質に大きく影響するため、しっかりと理解して実装することが重要です。

よくある質問(FAQ)

TypeError: Failed to fetch が出る原因は何ですか?

このエラーは、fetchリクエストがサーバーに到達しなかった場合に発生します。主な原因は以下の通りです。

  • ネットワークに接続されていない
  • URLが間違っている(例: スペルミスやポート番号の間違い)
  • CORS(クロスオリジンリクエスト)エラー
  • no-cors モードによってレスポンスの内容がブロックされている
  • HTTPSのAPIに対してHTTPからアクセスしている(Mixed Content)

解決策:

fetch('<https://api.example.com/data>')
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.error('Fetchエラー:', err.message));

チェックポイント:

  • URLが正しいか?
  • HTTPSを使用しているか?
  • サーバー側が正しくCORSを許可しているか?

fetch でPOSTリクエストしてもサーバーにデータが届きません

サーバーにデータが届かない場合、多くはContent-Typeヘッダーが適切に指定されていない、またはbodyの形式が正しくないケースです。

正しい書き方の例(JSON形式で送信する場合):

fetch('<https://api.example.com/users>', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ name: 'Taro', age: 30 })
});

注意点:

  • Content-Typeapplication/json にする
  • bodyJSON.stringify() で文字列化する

res.json() を使っても値が取れません

fetch のレスポンスは Promise なので、res.json()非同期的にデータを取得する必要があります。連続して .then() を使うか、async/await を使いましょう。

正しい例:

// thenチェーン
fetch('<https://api.example.com/data>')
  .then(res => res.json())
  .then(data => console.log(data));

// async/await
async function getData() {
  const res = await fetch('<https://api.example.com/data>');
  const data = await res.json();
  console.log(data);
}

まとめ

JavaScriptのfetchが「使えない」と感じている方も多いかもしれませんが、実はほとんどの問題には明確な原因と解決策があります。この記事では、fetchを使う上で遭遇しがちなトラブルから実践的な活用テクニックまで、幅広く解説してきました。

まず押さえておきたいのは、fetchの基本的な使い方を正しく理解することです。GETとPOSTリクエストの違い、レスポンスの適切な処理方法、そしてエラーハンドリングの重要性を理解することで、多くの問題を未然に防げます。

重要ポイント

エラー対処で最優先すべき項目:

  • CORSエラー:サーバー側でAccess-Control-Allow-Originヘッダーの設定を確認
  • Failed to fetchエラー:ネットワーク接続、URL、HTTPSとHTTPの混在をチェック
  • レスポンス処理:response.json()やresponse.text()の戻り値は必ずreturnまたはawaitで処理
  • HTTPステータス:response.okでステータスコードを確認してからデータ処理を行う

非同期処理で失敗しないためのポイント

  • thenチェーン:各then内で必ずreturnを使って値を次に渡す
  • async/await:try-catch文を使った適切なエラーハンドリングを実装
  • Promise.all:複数のAPIを並列処理することで処理時間を大幅短縮
  • タイムアウト設定:AbortControllerを使って適切な時間制限を設定

これらのポイントを意識するだけで、fetchの動作が格段に安定し、開発効率も向上します。

さらに、実際のプロジェクトではパフォーマンスの最適化も重要な要素です。キャッシュ機能の実装やストリーミング処理の活用により、ユーザー体験を大きく改善できます。特に大量のデータを扱うアプリケーションでは、これらのテクニックが必要不可欠となります。

fetchを使いこなすコツは、エラーが発生した時に慌てずに原因を特定する習慣を身につけることです。ブラウザの開発者ツールのNetworkタブやConsoleタブを活用し、どこで問題が発生しているかを正確に把握しましょう。多くの場合、エラーメッセージが解決への手がかりを提供してくれます。

最後に、fetchの学習は一度で完了するものではありません。新しいAPIとの連携や、より複雑な要件に対応する中で、徐々にスキルを積み上げていくものです。この記事で紹介したコード例を実際に動かしながら、自分なりのベストプラクティスを見つけていってください。

DOCTYPE html エラーの完全解決!なぜ必要?正しい書き方と頻出パターンを解説
HTML5開発に必須なDOCTYPE宣言の正しい書き方から、省略時に起こるCSSやJSの不具合、よくあるエラーの解決策を徹底解説。W3Cバリデータや開発ツールでのデバッグ方法、SEOやアクセシビリティへの影響まで、に関する悩みを完全解消!あなたのウェブ制作を次のレベルへ
popstateが発火しない原因と解決策を徹底解説!History APIと連携した正しい実装パターン&各ブラウザ対応法
popstateが発火しない原因や対処法にお困りではありませんか?本記事では、History APIとの関係性や、Chrome・Safari・Edgeなど主要ブラウザごとの挙動の違いを解説。発火しない主なケースやデバッグ方法、確実に動作させる実装方法までわかりやすくご紹介。popstateで悩んだときに役立ちます。
【初心者OK】npm audit fixとは?エラーの意味・--forceの使い方・解決しないときの対処法まで徹底解説
npm audit fixの基本から応用までを丁寧に解説する完全ガイド。脆弱性チェックの仕組みや自動修正の方法、--forceによる強制更新のリスク、よくあるエラーとその対処法を具体例付きで紹介します。さらにYarnユーザー向けの代替手段や、パッケージ更新前後の影響確認方法等も詳しく解説しています。
タイトルとURLをコピーしました