React axios の response インターセプター で n回リトライする

21794 ワード

React で axios を使用して Http 通信したときに、接続 Timeout が発生した場合は n 回リトライしたい。
axios の response インターセプターでできそうだと思ったのですが、盛大にハマってしまいました😭
やっと対応できたので記事にしておきます。
axiosのバージョン: v0.25.0

axios のインターセプターの基本的な使用方法

axios のインターセプターの基本的な使用方法については、コチラ React axios インターセプターでフックを使えるようにするで記載しましたので、軽く再掲しておきます。

axios 拡張用のファイルを作成します。
ファイル名はなんでもいいですが、axiosClient.ts というファイル名で作成しました。

  1. すべての HTTP リクエストに共通するデフォルト config を設定します
  2. すべてのリクエストに割り込み処理を入れるリクエストインターセプターを設定します。
    リクエストインターセプターはリクエストヘッダーにアクセストークンを設定したりといった用途に使われます。
  3. すべてのレスポンスに割り込み処理を入れるレスポンスインターセプターを設定します。
    レスポンスインターセプターはログを残したり、エラーを調整したりといった用途に使われます。
axiosClient.ts
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'

const BaseUrl = 'https://xxxxx.xxx.com'

/**
 * デフォルト config の設定
 */
export const axiosClient  = axios.create({
  baseURL: BaseUrl,
  timeout: 3000,
  headers: {
    'Content-Type': 'application/json'
  }
})

/**
 * リクエスト インターセプター
 */
axiosClient.interceptors.request.use((config: AxiosRequestConfig) => {
  if (config.headers !== undefined) {
    // --ヘッダにアクセストークンを埋める
    // const accessToken = getAccessToken()
    // if (accessToken) {
    //   config.headers.Authorization = `Bearer ${accessToken}`
    // }
  }
  return config
})

/**
 * レスポンス インターセプター
 */
axiosClient.interceptors.response.use(
  (response: AxiosResponse) => {
    return response
  },
  (error: AxiosError) => {
    switch (error.response?.status) {
      case 401:
        // なにかする
        break
      default:
        break
    }
    return Promise.reject(error)
   }
 )

使用するときは、axios.create メソッドで作成した axios インスタンスからリクエストを送信します。

+ import { axiosClient } from '@/_lib/axiosConfig'

export type Sample = {
  id: number
  name: string
}

export function useSampleApi() {

  const GetAll = async (): Promise<Sample[]> => {
    try {
+      const response = await axiosClient.get(`/api/Sample/GetAll`)
      return response.data
    } catch (error: unknown) {
      throw error
    }
  }

}

接続 Timeout が発生した場合に 1 回リトライする

まずは response インターセプターを使用して 1 回だけリトライできるように実装します。
接続 Timeout 時は エラーコード ECONNABORTED が返ります。
またエラーの config にリクエスト情報があるので、その config を利用して再度リクエストを送信するようにします。

axiosClient.ts のレスポンスインターセプター
axiosClient.interceptors.response.use(
  (response: AxiosResponse) => {
    return response
  },
  (error: AxiosError) => {
    // リトライ処理
+    if (error.code === 'ECONNABORTED') {
+        return axiosClient.request(error.config)
+    }
    return Promise.reject(error)
   }
 )

接続 Timeout が発生した場合に n 回リトライする

何回リトライするか?現在のレスポンスは何回目のリトライのものか?といった情報を保持するために AxiosRequestConfig を利用します。(コレがわからなかった!!)

▶ AxiosRequestConfig を拡張する

まずは AxiosReuestConfig を拡張して、何回リトライするかを保持する retries プロパティと、何回目のリトライかを保持するretryCount プロパティを追加します。
リトライ条件などを追加してもいいかもしれません。

ファイル名は何でもいいのですが、型定義ファイルは慣習的に xxxxx.d.ts とするのが一般的なので axios.d.tsとしました。

axios.d.ts
import "axios";

declare module "axios" {
  export interface AxiosRequestConfig {
    retries?: number;
    retryCount?: number;
  }
}

▶ レスポンスインターセプターの設定

axios.createメソッドの引数に指定する AxiosRequestConfig に、先ほど作成したプロパティretries(リトライ回数)retryCount(何回目のリトライか)に値を設定します。

axiosClient.ts
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'

const BaseUrl = 'https://xxxxx.xxx.com'

/**
 * デフォルト config の設定
 */
export const axiosClient: AxiosInstance  = axios.create({
  baseURL: BaseUrl,
  timeout: 3000,
  headers: {
    'Content-Type': 'application/json'
  },
+  retries: 3,
+  retryCount: 0,
})


/**
 * リクエスト インターセプター
 */
 axiosClient.interceptors.request.use((config: AxiosRequestConfig) => {
  console.log('axiosClient.interceptors.request.use')
  if (config.headers !== undefined) {
    // --ヘッダにアクセストークンを埋める
    // const accessToken = getAccessToken()
    // if (accessToken) {
    //   config.headers.Authorization = `Bearer ${accessToken}`
    // }
  }
  return config
},
(error: AxiosError) => {
  return Promise.reject(error)
})

/**
 * レスポンス インターセプター
 */
axiosClient.interceptors.response.use(
  (response: AxiosResponse) => {
    return response
  },
  (error: AxiosError) => {
    // リトライ処理
+    if (error.code === 'ECONNABORTED') {
+      if ((error.config.retries ?? 0) > (error.config.retryCount ?? 0)) {
+        error.config.retryCount  = (error.config.retryCount ?? 0) + 1
+        return axiosClient.request(error.config)
+      }
+    }
    return Promise.reject(error)
   }
 )

以上でリトライができました。

リクエストのUrlを修正すればコチラで動作確認できます。