1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

TypeScriptAdvent Calendar 2023

Day 2

[TypeScript] 非同期処理(Promiseを返すやつ)をリトライするよくあるコード(テストあり〼)

Last updated at Posted at 2023-12-07

これはなに?

Promise を返す関数(fetchとか)をリトライさせたいな、というときに使うコードです。

あちらこちらで何度も語られてきたものです。
今さら何書いてんのよ的な話ですみません。
お目汚し失礼します。

axiosrxjsも使いたいけど使えない(バンドルサイズがぁー)みたいなとき用です。

本体コード

type Operation<T> = () => Promise<T>;

interface PromiseRetryConfig {
  // エラー発生後のリトライ回数
  count: number;
  // エラー発生後の待ち時間(ms)
  delay: number;
}

const wait = (delay: number) =>
  new Promise((resolve) => setTimeout(resolve, delay));

/**
 * エラー時リトライする
 * @param operation
 * @param config
 * @returns
 */
export const promiseRetry = async <T>(
  operation: Operation<T>,
  config: PromiseRetryConfig
): Promise<T> => {
  try {
    return await operation();
  } catch (err) {
    if (config.count === 0) {
      throw err;
    }

    await wait(config.delay);

    return await promiseRetry(operation, {
      count: config.count - 1,
      delay: config.delay,
    });
  }
};

テストコード

import { promiseRetry } from "../src/libs/promise-retry";

describe("promiseRetry", () => {
  const wait = (delay: number) =>
    new Promise((resolve) => setTimeout(resolve, delay));

  it("常に成功", async () => {
    let executedCount = 0;

    // Given
    const operation = async () => {
      await wait(1);
      return ++executedCount;
    };
    const retryConfig = { count: 3, delay: 1 };

    // When
    const actual = await promiseRetry(operation, retryConfig);

    // Then
    expect(actual).toBe(1);
  });

  it("指定回数リトライするが失敗", async () => {
    let executedCount = 0;

    // Given
    const operation = async () => {
      await wait(1);
      throw ++executedCount;
    };
    const retryConfig = { count: 3, delay: 1 };

    try {
      // When
      await promiseRetry(operation, retryConfig);
      // ここには来ない
      expect.assertions(0);
    } catch (err) {
      // Then
      expect(err).toBe(4); // 初回+3回リトライ
    }
  }, 6000);

  it("リトライして成功する", async () => {
    let executedCount = 0;

    // Given
    const operation = async () => {
      await wait(1);

      // 3回目で成功
      if (++executedCount === 3) {
        return executedCount;
      } else {
        throw executedCount;
      }
    };

    const retryConfig = { count: 3, delay: 1 };

    // When
    const actual = await promiseRetry(operation, retryConfig);

    // Then
    expect(actual).toBe(3); // 初回+2回で成功
  });

  it("リトライさせないで失敗", async () => {
    let executedCount = 0;

    // Given
    const operation = async () => {
      await wait(1);
      throw ++executedCount;
    };
    const retryConfig = { count: 0, delay: 1 };

    try {
      // When
      await promiseRetry(operation, retryConfig);
      // ここには来ない
      expect.assertions(0);
    } catch (err) {
      // Then
      expect(err).toBe(1); // 初回のみ
    }
  });
});
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?