😃

【ReactTips】typescript で例外処理を高階関数で共通化する実装(Result型もあるよ)

2023/12/17に公開

はじめに

コードはこちら
あるプロジェクトでtry catchを複数箇所に記載しているチームがあったので、僕がコードレビューをした際に共通化するように指摘しました。

ちょうど上記の出来事があったタイミングでこちらの記事を読み、neverthrowというライブラリの存在を知ったので、今回は
- 普通に実装した場合
- Result型を使っで実装した場合

に分けてコードの違いを比較できればと思います。

普通に実装した場合

以下のようなコードになるイメージです。
withReaderがラッパーとなっており、関数を受け取る高階関数となってます。

import {useState,useEffect} from "react"

const fetcher = (url: string) =>async():Promise<string> =>  {
    const response = await fetch(url)
    const body = await response.json()
    return body.name as string
}

export const useDataFetch= (url :string) =>{
    const [data,setData] =useState<string>()
    useEffect(() => {
        (async()=>{
            const result = await withReader({ exec: fetcher(url) })
            setData(() => result)
        })()
    },[url])
    return {result: data}
}


type Props = {
    exec : () => Promise<string>
}
const withReader = async({exec}:Props) => {
    try{
        return await exec()
    }catch(err){
        throw new Error(err as string)
    }
}

Result型で実装した場合

こちらも一見先ほどと変わりがないですがPromise<Result<string, Error>>の部分が異なります。

そして、状態にセットする際も

result.match(
	(callback) => setData(() => callback), // 正常系
	() => console.log("err"),                            // 異常系
);

となり、Result型で定義した正常系と異常系それぞれ結果を定義してます。
個人的にはRustっぽさがあってResult型で守る方が好きです。

コードはこちら↓

import { useState, useEffect } from "react";
import { ok, err, Result } from "neverthrow";
const fetcher = (url: string) => async (): Promise<string> => {
	const response = await fetch(url);
	const body = await response.json();
	return body.name as string;
};

export const useDataFetchOfResultType = (url: string) => {
	const [data, setData] = useState<string>();
	useEffect(() => {
		(async () => {
			const result = await withReader({ exec: fetcher(url) });
			result.match(
				(callback) => setData(() => callback),
				() => console.log("err"),
			);
		})();
	}, [url]);
	return { result: data };
};

type Props = {
	exec: () => Promise<string>;
};
const withReader = async ({ exec }: Props): Promise<Result<string, Error>> => {
	try {
		return ok(await exec());
	} catch (e) {
		return err(new Error(e as string));
	}
};

まとめ

コードでの説明が多くなりましたが、ベタ書きされがちな例外処理を高階関数を使って共通化する例となりました。

Discussion