Theme
SD MILIEU

2021-5-18

Result型とtry~catchどちらを利用するか

結論

HTTPエラーなど、明らかにどこかで処理すべき例外に関しては、Result型で対応する。

例えば、自分は現在通信をする際は以下のような関数を作成している。

type NetworkResult = Result<unknown, HttpError>

type HttpRequestOption<T> = {
  url: string
  method: 'get' | 'post' | 'put' | 'patch' | 'delete'
  params?: URLSearchParams
  data?: T
}
export const httpRequest = async <T>(
  params: HttpRequestOption<T>
): Promise<NetworkResult> => {
  try {
    const response = await axios({
      method: params.method,
      url: params.url,
      params: params.params,
      data: params.data,
    })

    return new Success(response.data)
  } catch (error) {
    const isHttpError = error instanceof AxiosError
    if (!isHttpError || error.response === undefined) {
      // 通信エラー以外は想定外の例外なので例外として再スロー
      throw error
    }

    if (error.response.status === 400) {
      const errorBody = error.response.data as ApiErrorBody.BadRequest

      return new Failure(
        new HttpError({
          type: 'bad_request',
          data: errorBody,
        })
      )
    }

    return new Failure(new HttpError({ type: 'general' }))
  }
}

理由

例えば、APIを叩いてユーザーを取得する fetchUser という関数があるとする。これが通信失敗時に例外を投げる場合は

try {
  await fetchUser(id)
} catch (error) {
  if (error instanceOf HttpError) {
    // 通信エラー時の処理
    if (error.status === 404) {
      // 中略
    }

    return
  }

  // 想定外の例外を握りつぶさないように再スローする必要がある
  throw error
}

このコードには2点しんどい部分がある。

  1. 通信エラー時に投げられるエラーの型を知っておく必要がある
  2. 想定外の例外を握りつぶさないよう再スローしないといけない

これがResult型であれば。

const result = await fetchUser(id)

if (result.isFailure()) {
  // 失敗時の処理
  if (result.error.status === 404) {
    // 中略
  }
} else {
  // 成功時の処理
}

このようになり、この時のエラーの型に関してはエディタが教えてくれるし、例外を握りつぶさないよう気をつける必要がない。

基本的にtry〜catchはどのようなエラーが飛んでくるかをドキュメントなり読まないとわからないのでしたくない。極力try〜catch無しでコードが書けるような作りにしたい。