nove-b

レスポンス429の時に成功するまでリクエストをし続ける

Created: March 19, 2025

Modified: April 12, 2025

運用こそ正義

ありがたいことに運用中のサービスを使ってくれる人が、本当にぼちぼちだが増えたきた……、気がする。

https://kindle-unlimited-search.nove-b.dev/

ただまだいくつか問題点があり、今回はその1つ「ステータスコードがしっかり定義されていない」という問題を解決した。

現状のサービスだと

という起こりえるエラーをすべて400のサーバーエラーでフロント側に返していた。

これだと、フロント側でユーザーに対して異なるメッセージを提供することができず、全く親切じゃない。

しかもフロントは400が返ってくる前でリクエストをし続けるという、こちらも全く親切じゃない実装をしていた。

ステータスコードを定義した

そこで、該当する作家が見つからない時は404を、ページが範囲害外の時は416を、そしてリクエスト制限の時は429を返すようにした。

そしてフロントではその値で処理を分けることにした。

404の時は「検索した作家の作品が見つかりませんでした。」というスナックバーを出し、416の時はつまり、すべてのページを確認したということなので「作品(最大100件)の取得が完了しました。」というスナックバーを出し、429の時は再度リクエスト制する(5回まで挑戦する)という仕様に書き換えた。

リクエストを再試行する

429のように再度リクエストするコードをプラグインなしで初めて書いてみたので備忘録してまとめておく。

初めて知ったんだけど、指数バックオフという手法を取ることにした。

指数バックオフとは

リトライ(再試行)する間隔を指数関数的に増加させる手法

とのことらしい。

つまり初回のリトライに失敗したら1秒後に再試行、2回目失敗したら2秒後に再試行、3回目失敗したら4秒後…といった具合に増加していく。

これに加えて今回は5回までリトライして接続できなかった時は処理を中断させることにした。

const fetchAPI = async () => {
  setState("wait");
  let page = 1;
  let retryCount = 0;
  const maxRetryCount = 5; // 最大リトライ回数

  while (true) {
    // page が 10 を超えた場合はループを終了
    if (page > 10) {
      setState("end");
      console.log("成功");
      break;
    }

    try {
      const response = await fetch(`${endpoint}$`);

      if (response.ok) {
        console.log("データ取得に成功しました。");
        page++; // 次のページへ
        retryCount = 0; // リトライ回数をリセット
        await new Promise((resolve) => setTimeout(resolve, 1000)); // 1秒待機
      } else if (response.status === 429 && retryCount < maxRetryCount) {
        // 429 Too Many Requests が返された場合、リトライ
        const delay = Math.pow(2, retryCount) * 1000; // 指数バックオフ
        console.log(
          `リクエストが多すぎます。${delay / 1000}秒後にリトライします。`,
        );
        await new Promise((resolve) => setTimeout(resolve, delay));
        retryCount++;
      } else {
        // その他のエラーの場合、エラーログを出力してループを終了
        console.error(
          `エラーが発生しました。ステータスコード: ${response.status}`,
        );
        setState("end");
        toast.error("失敗");
        break;
      }
    } catch (error) {
      console.error("失敗:", error);
      setState("end");
      break;
    }
  }
};

まず最大10ページまでページが10以下かどうかの判定をする。

ページが10より大きい時はその時点で処理を終了する。

10以下の場合はapiを叩き、成功したら、1秒待ちページに1を足してループ処理の先頭に戻る。

429が返ってきた時、指数バックオフの値を定義し、定義した時間待ち、リトライカウントに1を足し、ループ処理の先頭に戻る。

この時点でページに1を足さないので、また同じページの再リクエストができる。

新着記事

Search