📘

サーバーサイドリクエストのエラーハンドリングをaxios.interceptorsでいい感じにした

2024/12/08に公開

サーバーサイドリクエストのエラー処理のコードを毎回繰り返し実装していたので、axios.interceptorsを使っていい感じに共通化できないか検討したお話です。

構成: React + Typescript + Axios
Axiosのバージョン: v1.7.8

毎回これ書いてるな

ある日、ふとサーバーサイドリクエストの処理を実装しているときに、毎回同じエラーハンドリングを記述していることに気づきました。

async (params: SomeRequestParameter) => {
  try {
    const res = await apiRequest(params)
    setData(res)
  } catch (error) {
        if (isServerSideError(error)) {
            handleServerSideError(error)
          } else {
            throw error
          }
    }
}

作りとしては、まずapiRequest内でAxiosによってバックエンドのエンドポイントにリクエストを投げ、問題がなければ返ってきたレスポンスデータを保持する、といったようなものです。
リクエスト時にerrorが生じた場合はそれを捕捉し、isServerSideError()によってバックエンドの処理エラーなのか、それ以外のフロントエンド起因のエラーなのかを判定し、handleServerSideError()でよしなに処理しています。

たまにエラーコードに応じてカスタムハンドリングが入っていることもあるのですが、基本的には同じ作りです(簡単のため簡略化しています)。

気になる

毎回同じ処理を実装するのは非効率ですし、実装漏れが発生するリスクが高まります。ヒューマンエラーによるバグの温床にもなりかねません。

共通化したくなってきました。

こうすると共通化できそう

apiRequest は、サーバーサイドへのリクエストを処理する際に、内部で Axios を利用した共通の HTTP リクエスト処理 httpRequest を呼び出します。

apiRequest.ts
import httpRequest from '@/gateways/httpRequest'
...
export const apiRequest: async (id: string) => {
    const { data } = await httpRequest<{
      dataA: AProps
      dataB: BProps
      dataC: CProps
    }>({ url: path.to.api.endpoint(id) })

    return data
  },
}

httpRequest内はAxiosに外部から受け取ったデータを渡すのみの役割です。

httpRequest.ts
import axios from 'axios'

type Props = {
  method?: 'get' | 'post' | 'put' | 'patch' | 'delete'
  url: string
  data?: any
}

export default <T>({ method = 'get', url, data }: Props) => (
    axios<T>({
        method,
        url,
        data,
      })
)

この共通のHTTPリクエスト処理にエラーハンドリングを組み込むことで全体の共通化ができそうなので、Axiosの機能を調べたところ、interceptors というAPIがありました。
このAPIは、responseが then や catch で処理される前に、個別の割り込み処理を追加できる便利な機能を提供します。

https://axios-http.com/docs/interceptors

これを使ってみましょう。

こうなったよ

サーバーサイドエラーの共通的なエラーハンドリングを interceptors の response 処理内に移動させました。

httpRequest.ts
import axios, { AxiosRequestConfig } from 'axios'

type Props = {
  method?: 'get' | 'post' | 'put' | 'patch' | 'delete'
  url: string
  data?: any
}

export default <T>({ method = 'get', url, data }: Props) => {
  const config: AxiosRequestConfig = {
    method,
    url,
    data,
  }

  const interceptInstance = axios.create()
  interceptInstance.interceptors.response.use(
    (response) => response,
    (error) => {
      // もともと個別のリクエスト処理で行っていたエラーハンドリング
      if (isServerSideError(error)) {
        handleServerSideError(error)
      }
      throw Error
    },
  )

  return interceptInstance<T>({ ...config })
}

もともと個別でエラーハンドリングを実装していた箇所もtry-catchが不要になり、だいぶスッキリしましたね。

async (params: SomeRequestParameter) => {
    const res = await apiRequest(params)
    setData(res)
}

おまけ:共通エラー処理を通ってほしくない場合もあるよね

共通エラー処理をスキップしたい場合、Axiosのconfigオブジェクトを利用する方法や、Axiosインスタンスを切り替える方法があります。

configオプション

今回の実装ではAxiosのwrapperを用意していてこちらの方法は取れませんが、Axiosではaction呼び出しの際にconfigを通じて参照可能な引数を渡すことも可能です。

httpRequest.ts
  interceptInstance.interceptors.response.use(
    (response) => response,
    (error) => {
      // error.configを通して、axiosの呼び出し元で引数として独自に設定した割り込みフラグ`intercept`が参照可能
      if (error.config.intercept && isServerSideError(error)) {
        handleServerSideError(error)
      }
      throw Error
    },
  )

フラグによって使うAxiosインスタンスを変える

今回のようにAxiosインスタンスの生成も丸々リクエスト処理のWrapper関数に内包している場合は、割り込み処理を行うかどうかのフラグ(今回はintercept)を引数として渡し、そのフラグによって返すAxiosインスタンスを切り替える方法も取れます。

httpRequest.ts
import axios, { AxiosRequestConfig } from 'axios'

export default <T>({ method = 'get', url, data, intercept = true }: Props) => {
  const config: AxiosRequestConfig = {
    method,
    url,
    data,
  }

  const interceptInstance = axios.create()
  interceptInstance.interceptors.response.use(
    (response) => response,
    (error) => {
      if (isServerSideError(error)) {
        handleServerSideError(error)
      }
      throw Error
    },
  )

// 返すAxiosインスタンスをフラグによって切り変える
return intercept ? interceptInstance({ ...config }) : axios({ ...config })

さいごに

スッキリと共通化ができたと思います。

今回の共通ハンドリングに加え、前後にカスタム処理を割り込ませる方法も検討していますが、長くなるのでまた別の記事で取り上げたいと思います。

以上、axios.interceptorsで共通処理をいい感じにしたお話でした。

この記事は SmartHR Advent Calendar 2024 シリーズ2の8日目の記事でした。

Discussion