ゼロから始めるプログラム言語サイトのトップへ

第19回 API編 第3話 地雷原に、帰り道をつける

第19回。全8ページを続けて読めます。

学習レクチャー・2026年9月15日更新

第19回 API編 第3話
地雷原に、帰り道をつける

失敗を受け止める

パソコンでは動いた。

KAZU「完成!」

ところがiPhoneで試すと、思ったように動かない。

KAZU「さっき完成したところなんだけど。」

API「別の環境でも確認できて、よかったですね。」

KAZU「励ましの角度が独特だね。」

通信する機能では、自分のコードだけでなく、ネットワークや相手のサービス、ブラウザの制約なども関わる。動かない原因がAPIとは限らない。ボタンの処理や画面の更新に問題がある場合もある。

だから、「APIが悪い」と決める前に、どこまで進んだかを確かめたい。

ボタンは反応したか。通信は送られたか。返事は来たか。受け取ったデータを表示できたか。

KAZU「道路が悪いと思ったら、玄関のドアが開いていないこともあるのか。」

API「あります。」

そして、原因を直すだけでなく、失敗したときの動きも用意する。

次の例は、前回と同じ窓口が存在する前提だ。

async function loadExample() {
  try {
    const response = await fetch("/api/example");

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

    const data = await response.json();
    console.log("取得できました", data);

  } catch (error) {
    console.error("取得できませんでした", error);
  }
}

loadExample();

HTTPエラーとは、通信先のサーバーからHTTPの返事が届いたものの、その返事が「処理を完了できなかった」ことを示している状態だ。たとえば、指定したページが見つからないときは404、サーバー側で問題が起きたときは500になる。

response.ok は、返事が成功扱いかどうかを確認するための値だ。ステータスが200番台なら通常は true、404や500のようなHTTPエラーなら false になる。

ただし、response.ok が false になっただけでは、catch へ自動的に移るわけではない。そこで throw new Error(...) を使い、こちらから「これは失敗として扱います」と知らせる。

throw は、問題が起きたことを知らせて、通常の処理を中断する指示だ。たとえるなら、受付で「この依頼は処理できません」と分かった時点で、次の作業を止め、非常口へ案内するようなものだ。

try の中で処理を行い、そこで起きた例外を catch で受け止める。例外とは、プログラムの途中で起きた予想外の問題を知らせる仕組みである。

たとえば、JSONではなく壊れた文字列が返ってきた場合、response.json() の読み取りに失敗して例外が起きる。ネットワーク自体に問題がある場合も、fetch() が例外になることがある。catch があれば、こうした問題を受け止め、エラー表示へ進められる。

学習レクチャー・2026年9月15日更新

第19回 API編 第3話
地雷原に、帰り道をつける

ネットワークエラーとHTTPエラー

ここで、ネットワークエラーとHTTPエラーは区別して考えよう。

ネットワークエラーは、サーバーからHTTPの返事を受け取る前に起きる問題だ。Wi-Fiが切れた、DNSで接続先を見つけられない、通信がタイムアウトしたなどがある。この場合、fetch() 自体が例外になり、response は作られない。

一方、404や500は、サーバーからHTTPの返事が届いたうえで、その内容が「見つからない」「サーバー側で問題が起きている」という意味を示している状態だ。この場合、fetch() は返事を受け取るため、通常は response が作られる。ただし response.ok は false になる。

つまり、次のように整理できる。

  • ネットワークエラー:返事そのものを受け取れない。fetch() が例外になり、catch へ進む
  • 404:返事は届いたが、指定したページやAPIが見つからない
  • 500:返事は届いたが、サーバー側で問題が起きている
  • 404や500などのHTTPエラー:response.ok を確認し、必要なら throw して catch で扱う

流れを短く言うと、こうだ。

「返事を受け取る」→「response.ok でHTTP上の成功か確認する」→「HTTPエラーなら throw する」→「catch が受け止める」。

ただし、ネットワークエラーの場合は、返事を受け取る前に fetch() が失敗する。そのため、response.ok を確認するところまで進まず、直接 catch へ移る。

KAZU「ネットワークエラーは返事が来ない。404や500は、返事は来たけれど内容が失敗なんだね。」

API「その違いを分けると、再試行するか、設定を直すかを判断しやすくなります。」

学習レクチャー・2026年9月15日更新

第19回 API編 第3話
地雷原に、帰り道をつける

種類に応じたエラー表示

たとえば、エラーの種類を分けて表示するなら、次のように書ける。

async function loadExample() {
  try {
    const response = await fetch("/api/example");

    if (response.status === 404) {
      throw new Error("指定したAPIが見つかりません");
    }

    if (response.status >= 500) {
      throw new Error("サーバー側で問題が起きています");
    }

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

    const data = await response.json();
    console.log("取得できました", data);

  } catch (error) {
    console.error("取得できませんでした", error);
  }
}

この例では、404と500以上を先に分けている。404ならURLや窓口の設定を確認する必要がある。500以上なら、サーバー側の復旧を待つ、管理者へ連絡するなどの対応が考えられる。

ただし、実際のアプリでは、利用者にHTTPステータスをそのまま見せるのではなく、「情報が見つかりません」「サーバーが混み合っています」など、次の行動が分かる言葉に置き換えることも大切だ。

KAZU「同じ失敗でも、直し方が違うんだね。」

API「はい。返事がないのか、返事はあるが失敗なのかを見分けます。」

ただし、このコードの表示先はコンソールだ。実際の利用者には、画面上で「取得できませんでした。時間をおいて再度お試しください」などと知らせる必要がある。

KAZU「押したのに無言、が一番困るんだよ。」

API「処理中か、成功したか、失敗したかが分かると、次の行動を選べます。」

学習レクチャー・2026年9月15日更新

第19回 API編 第3話
地雷原に、帰り道をつける

再試行の判断とコード

再試行とは、失敗した処理をもう一度やり直すことだ。ただし、何でも繰り返せばよいわけではない。まず、エラーを「再試行しても直りにくいもの」と「時間を置けば直る可能性があるもの」に分ける。

404のようにURLやAPIの指定が間違っているエラー、400番台の入力ミス、401や403のような認証・権限の問題は、同じ条件で繰り返しても解決しにくい。まずURL、入力値、認証情報、権限などを直す必要がある。

一方、ネットワークエラーや、429(短時間にアクセスしすぎたことを示すエラー)、一部の500番台エラーは、一時的な問題の可能性がある。時間を置いて再試行する設計を検討できる。ただし、500番台でも、処理の内容やAPIの仕様によっては再試行が安全とは限らない。

つまり、すべてのHTTPエラーを一律に再試行してはいけない。ステータスコードだけでなく、処理の種類、APIの仕様、エラーの内容を確認して判断する。

たとえば、天気を読むだけの処理なら、ネットワークエラーや一時的なサーバーエラーが起きたときに、回数を制限して再試行する設計は考えやすい。

function isRetryableStatus(status) {
  return status === 408 ||
         status === 429 ||
         status >= 500;
}

function sleep(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
}

async function loadWeather() {
  const maxAttempts = 3;

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      const response = await fetch("/api/weather");

      if (!response.ok) {
        const error = new Error(
          `HTTPエラー: ${response.status}`
        );
        error.status = response.status;
        throw error;
      }

      return await response.json();

    } catch (error) {
      const isNetworkError =
        error instanceof TypeError && error.status === undefined;

      const isRetryableHttpError =
        Number.isInteger(error.status) &&
        isRetryableStatus(error.status);

      const canRetry =
        isNetworkError || isRetryableHttpError;

      if (!canRetry || attempt === maxAttempts) {
        throw error;
      }

      const baseDelay = 1000;
      const exponentialDelay =
        baseDelay * 2 ** (attempt - 1);

      const jitter =
        Math.floor(Math.random() * 300);

      const delay = exponentialDelay + jitter;

      console.warn(
        `${attempt}回目の失敗。` +
        `${delay}ms後に再試行します`
      );

      await sleep(delay);
    }
  }
}

この例では、最大試行回数を3回にしている。最初の試行を1回目と数え、1回目に失敗したら待って2回目、2回目に失敗したら待って3回目を試す。3回目も失敗したら、それ以上は繰り返さず、最後のエラーを呼び出し元へ伝える。

学習レクチャー・2026年9月15日更新

第19回 API編 第3話
地雷原に、帰り道をつける

回数・待ち時間・上限

再試行回数には、最初の試行を含めるかどうかで意味が変わる。ここでは maxAttempts = 3 を「合計3回試す」という意味にしている。設計書や画面表示でも、試行回数の数え方をそろえておく。

isRetryableStatus() では、408、429、500番台を再試行候補にしている。408はリクエストの処理が時間内に終わらなかったことを示す。429はアクセス回数の制限に達したことを示す。500番台はサーバー側の一時的な障害の可能性がある。

ただし、これはあくまで一例だ。APIによっては、503だけを再試行対象にしたり、429ではレスポンスの Retry-After ヘッダーに従ったりする必要がある。429を受け取ったら、固定の待ち時間ではなく、サーバーが指定した待ち時間を優先する設計も考えられる。

また、ネットワークエラーを TypeError だけで判定する方法は、環境によっては十分ではない。fetch() が返すエラーの種類や、アプリ側で定義したエラー情報を確認し、必要なら独自のエラー分類を用意する。ここでは、ネットワークエラーとHTTPエラーを区別する考え方を示すために簡略化している。

待ち時間には、指数バックオフを使っている。

baseDelay * 2 ** (attempt - 1) によって、待ち時間はおおむね1秒、2秒、4秒と伸びる。障害中のサーバーへ、短い間隔で何度もアクセスし続けることを避けやすい。

さらに、ランダムな短い時間を加えている。これをジッターという。同じ時刻に多くの利用者が一斉に再試行するのを避けるためだ。

ただし、待ち時間を無制限に伸ばしてはいけない。実際のアプリでは、1回あたりの最大待ち時間や、全体のタイムアウト時間も設ける。利用者を長く待たせすぎないよう、再試行中は画面に「再接続しています」などと表示し、最終的に失敗したら次の行動を案内する。

KAZU「再試行には、対象にしてよいエラー、回数、待ち時間、上限があるんだね。」

API「はい。『もう一度』を自動化する前に、どの失敗なら安全かを決めます。」

また、500番台でも、処理の内容によっては再試行が危険になる。天気の取得のように読むだけの処理なら比較的安全だが、注文や決済では、サーバーが処理を受け付けた後に返事だけ届かなかった可能性がある。

学習レクチャー・2026年9月15日更新

第19回 API編 第3話
地雷原に、帰り道をつける

二重注文・二重決済を防ぐ

KAZU「ネットワークエラーなら、サーバーが何もしていないとは限らないのか。」

API「はい。返事が届かなかっただけで、裏側では処理が成功している場合があります。」

さらに、再試行には回数だけでなく、処理の性質も関係する。

注文や決済のように、サーバーの状態を変更する処理を、クライアント側から単純に自動再試行してはいけない。通信が切れた時点で、サーバーが処理を受け付けたかどうか分からないことがあるからだ。

KAZU「画面では失敗に見えても、裏では成功していることがあるのか。」

API「通信が切れた場所によっては、そういうことがあります。」

この場合、単純な再試行は危険だ。天気の取得なら「もう一度聞く」だけで済むが、注文は「もう一度実行する」ことになる。

二重処理とは、本来一回だけ行う仕事が、誤って二回行われることだ。たとえば、サーバーでは注文を受け付けたのに、返事が届く前に通信が切れることがある。画面に「失敗したかもしれません」と表示され、利用者がもう一度押すと、同じ商品が二つ注文される可能性がある。

そのため、注文や決済では、まず「再試行してよい処理」と決めつけず、注文履歴や決済状態を確認する。必要なら、サーバー側に状態確認用のAPIを用意し、元の依頼が処理済みか、未処理か、不明かを確認してから次の操作を決める。

また、同じ依頼を識別するための一意な注文番号やリクエストIDを使い、同じIDの依頼を二回受けても一回分として扱う仕組みを用意する。これは冪等性を確保するための設計である。決済や注文では、サーバー側でこの重複防止を行うことが重要だ。

KAZU「同じ依頼に同じ番号をつけて、二回届いても一回として扱うのか。」

API「はい。ただし、その仕組みがサーバー側で正しく実装されている必要があります。」

利用者が「注文する」ボタンを押したら、画面側でボタンを一時的に無効にする方法もある。ただし、これは連打を防ぐ助けにはなるが、通信切れによる再送まで完全に防ぐものではない。ボタンを無効にしただけで、二重注文を防げたと考えてはいけない。

注文や決済で通信が切れた場合は、「失敗したので、もう一度押してください」とすぐ案内するのではなく、「処理結果を確認しています」「注文履歴を確認してください」など、重複を避ける案内を優先する。状態が不明なまま、同じ処理を自動再試行しない。

一方、天気の取得のように読むだけの処理なら、再試行による二重注文の心配は比較的小さい。処理の種類に応じて、再試行してよいかを判断することが大切だ。

KAZU「再試行は、『もう一度聞く』のか、『もう一度実行する』のかで危険度が違うんだね。」

API「はい。読む処理と、注文・決済のように状態を変える処理は分けて考えます。」

学習レクチャー・2026年9月15日更新

第19回 API編 第3話
地雷原に、帰り道をつける

動作確認と帰り道

待ち時間の上限、再試行の回数、以前のデータを見せる場合の更新日時。こうしたルールを決めるほど、使う人が困る場面を減らせる。

KAZU「まさにAPIは地雷原だね。」

API「だから地図と帰り道を作りましょう。」

「動いた」は大きな一歩だ。

けれど、ゴールの合図ではない。パソコンだけでなくiPhoneでも、通信が速いときだけでなく遅いときにも試す。成功時だけでなく、ネットワークが切れたとき、404が返ったとき、429が返ったとき、500が返ったとき、返事が遅れたときにも確認する。

動作確認とは、一度動いたと喜ぶことではない。どんな条件なら動き、どんな条件で失敗するのかを知ることだ。

失敗は起きるものとして、処理中・成功・失敗の状態を画面に示す。失敗時には、何が起きたかだけでなく、次に何をすればよいかも伝える。ネットワークエラーなら「接続を確認して、もう一度取得する」、404なら「設定やURLを確認する」、429なら「しばらく待ってから再度試す」、500なら「時間をおいて再度試す」など、原因に合った案内が必要だ。

注文や決済で結果が不明な場合は、「もう一度実行する」ではなく、「注文履歴や決済状態を確認する」と案内する。処理が完了していないことを確認できた場合に限り、重複を防ぐ仕組みを使って再実行を検討する。

KAZU「処理の種類だけでなく、失敗の種類によっても帰り道の案内が変わるんだね。」

API「はい。迷った人を同じ場所へ何度も走らせないことが大切です。」

一つ直ると感動する。三つ直れば、ちょっと自慢したくなる。けれど、その日の最後にもう一度、パソコンとiPhoneを手に取る。

「動いた」は大きな一歩。

その一歩を完成へつなげるのは、動作確認と、失敗したときの備えだ。

完成の声は、成功した画面だけでなく、ネットワークエラー、404、429、500などの失敗画面にも、それぞれの帰り道があることを確かめてからでも遅くない。

学習レクチャー・2026年9月15日更新

第19回 API編 第3話
地雷原に、帰り道をつける

ミニテストと実習

【ミニテスト】

APIの処理が失敗したら、何でも無制限に再試行すればよい?

A.よい
B.原因や処理の内容に応じて判断する

答え:B。一時的なネットワークエラー、429、または一部のサーバー側の一時的な障害なら、回数と待ち時間を制限して再試行すると直ることがある。一方、404のようなURLの間違い、入力ミス、認証情報や権限の問題には設定や入力の修正が必要だ。

注文や決済では、通信が切れてもサーバー側で処理が成功している可能性がある。結果が不明なまま同じ処理を再試行すると、二重注文や二重決済になるおそれがあるため、まず注文履歴や決済状態を確認し、サーバー側の重複防止の仕組みも利用する。

【小さな実習】

自分の画面について、「処理中」「成功」「失敗」の三つの表示を考えよう。パソコンとスマートフォン、通信が速いときと遅いときでも試してみる。

失敗したときは、「何が起きたか」だけでなく、「次に何をすればよいか」も表示する。ネットワークエラー、404、429、500などで、再試行してよいのか、URLや設定を直すのか、時間をおいて待つのかを考えよう。

再試行する場合は、対象にするエラー、合計の試行回数、指数バックオフによる待ち時間、最大待ち時間を決める。無限に繰り返さず、最後は利用者へ分かる形で失敗を伝える。

注文や決済のように状態を変更する処理では、通信が切れたときに自動再試行しない。まず処理結果や履歴を確認し、同じ依頼を一回だけ扱うための注文番号やリクエストIDなど、サーバー側の重複防止策があるかを確認する。

処理とエラーの種類に合った案内を用意し、利用者が迷わず戻れる道を作るところまでが、画面づくりの仕事だ。