📖

async-retryを使ったTypeScriptでのリトライ+テスト

2024/04/28に公開

TypeScriptでAPIリクエストなどを行う際に、一時的なサーバーエラーを考慮してリトライしたいときがあります。そんなときに便利なasync-retryの使い方とテストの書き方をまとめました。

async-retry

Vercel製のasync-retryを使うとリトライ処理を最大回数や待ち時間を考慮して簡単に書くことができます。リトライ処理自体ははライブラリを用いなくても書くことはできますが、必要な機能が揃っていそうなので使ったほうが便利かと思います。

インストール

TypeScriptで使う場合は型定義もインストールしましょう。

npm i async-retry
npm i -D @types/async-retry

使い方

import retry from "async-retry";

const res = await retry(
      // 実行対象の処理
      async (bail) => {
        const res = await fetch("https://www.example.com/");

        if (!res.ok) {
            throw new Error("リトライ");  // Errorをthrowするとリトライされる
          } else {
            bail(new Error("回復できないエラー")); // bailにErrorを渡すとリトライせずに終了
          }
        }

        return res;
      },
      // オプション
      {
        retries: 4,
      },
    );

    const record = (await res.json())

オプションはこちらに書かれています。
少し注意が必要なのは、retriesに指定するのは「リトライする最大回数」です。実際には、はじめの1回+retriesに指定した回数の合計回数実行される可能性があります。(上記では最大5回)

テスト

テストはいつもと同じように書くことができます。
ただし、そのままだとリトライする度に待ち時間が増えるためテスト完了まで時間がかかってしまいます。このため、テストの実行時だけ待ち時間を減らすようにします。

// **.test.ts

import retry from "async-retry";

// リトライにかかる時間を短縮
jest.mock("async-retry", () => {
  const originalModule = jest.requireActual("async-retry");

  return {
    ...Object.assign({}, originalModule),
    __esModule: true,
    default: async (func: retry.RetryFunction<unknown>, options: retry.Options) => {
      return await originalModule(func, {
        ...options,
        factor: 1,
        maxTimeout: 10, // 最大タイムアウト時間を減らす
        minTimeout: 10, // デフォルトのままではタイムアウト時間がmax > minになってしまうので、minも減らす
      });
    },
  };
});

上記の書き方だと、テストでは途中でbailによって処理を終了できなくなってしまいます。原因がわからないのですが、テストなので許容しています。

まとめ

async-retryを使うとリトライに必要な機能をすぐに書けることがわかりました。テストを書く際は待ち時間を減らして、実行時間を減らす工夫が必要と思います。

Discussion