本文へスキップ
JavaScript 応用 JavaScript エラー処理 fetch

通信エラーを適切に処理しよう

更新日:2026-08-02

通信は、URLの間違い、サーバー障害、ネットワーク切断などで失敗します。開発者向け情報と利用者向け表示を分けて扱います。

前回はfetch()の基本を学びました。今回は失敗を「HTTPエラー」「ネットワークエラー」「データ形式のエラー」に分け、再試行できる読み込み画面へ改善します。

今回使用するファイル構成

javascript-practice/
├── index.html
└── js/
    └── script.js

通信失敗を処理する流れ

response.okを確認する

async function fetchArticles() {
  const response = await fetch('/api/articles');

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

  return response.json();
}

response.okはHTTPステータスが200番台ならtrueです。404や500を成功データとして処理しないよう明示的に確認します。

tryとcatchで失敗を扱う

async function loadArticles() {
  try {
    const articles = await fetchArticles();
    renderArticles(articles);
    showStatus('読み込みが完了しました');
  } catch (error) {
    console.error(error);
    showStatus('記事を取得できませんでした。時間をおいてお試しください。');
  }
}

Consoleには調査用の詳細、画面には利用者が次に取れる行動を表示します。スタックトレースや内部URLをそのまま画面へ出しません。

finallyで後処理する

try {
  loadButton.disabled = true;
  await fetchArticles();
} catch (error) {
  console.error(error);
} finally {
  loadButton.disabled = false;
}

finallyは成功・失敗にかかわらず実行されます。

エラーの種類を区別する

HTTPエラー

サーバーからレスポンスは届いたものの、要求が成功しなかった状態です。代表例は404と500です。

  • 400:送った内容に問題がある
  • 401:認証が必要
  • 403:操作する権限がない
  • 404:URLに対応するデータがない
  • 500:サーバー内部で問題が発生した

fetch()はレスポンスを受け取れているため、404や500だけではPromiseを拒否しません。response.okを自分で確認します。

ネットワークエラー

オフライン、名前解決の失敗、接続拒否など、レスポンス自体を受け取れない状態です。この場合はfetch()が拒否され、catchへ移ります。

データ形式のエラー

成功レスポンスでも、壊れたJSONや想定外の構造が返る場合があります。response.json()の失敗やArray.isArray()による検査も同じcatchで扱えます。

再試行できる画面を完成させる

<button id="load-button" type="button">記事を読み込む</button>
<button id="retry-button" type="button" hidden>もう一度試す</button>
<p id="status" aria-live="polite"></p>
<ul id="article-list"></ul>
const loadButton = document.querySelector('#load-button');
const retryButton = document.querySelector('#retry-button');
const status = document.querySelector('#status');
const articleList = document.querySelector('#article-list');

async function fetchArticles() {
  const response = await fetch('data/articles.json');

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

  const articles = await response.json();

  if (!Array.isArray(articles)) {
    throw new TypeError('記事データが配列ではありません');
  }

  return articles;
}

function renderArticles(articles) {
  articleList.replaceChildren();

  articles.forEach((article) => {
    const item = document.createElement('li');
    item.textContent = article.title;
    articleList.append(item);
  });
}

async function loadArticles() {
  status.textContent = '記事を読み込んでいます。';
  loadButton.disabled = true;
  retryButton.hidden = true;

  try {
    const articles = await fetchArticles();
    renderArticles(articles);
    status.textContent = `${articles.length}件を読み込みました。`;
  } catch (error) {
    console.error('記事取得に失敗しました', error);
    status.textContent = '記事を取得できませんでした。通信環境を確認して、もう一度お試しください。';
    retryButton.hidden = false;
  } finally {
    loadButton.disabled = false;
  }
}

loadButton.addEventListener('click', loadArticles);
retryButton.addEventListener('click', loadArticles);

利用者向けメッセージには、内部URLやスタックトレースを表示しません。開発者が調査する詳細はconsole.error()へ残します。

DevToolsで失敗を再現する

正常系だけでなく、意図的に失敗させて確認します。

  1. URLをarticles-missing.jsonへ変えて404を発生させる
  2. JSONの末尾へ余分なカンマを付けて解析を失敗させる
  3. DevToolsのNetworkタブをOfflineにして通信を試す
  4. どの場合もボタンが再び操作可能になることを確認する

エラー確認後は、URL、JSON、ネットワーク設定を元に戻してください。

まとめ

HTTP結果はresponse.ok、例外はtrycatchで処理します。次の記事ではコードをモジュールへ分割します。