Result型って何ですか
RustやSwiftだと馴染み深いResult
型とは、その名の通り結果を表すものです。
TypeScriptだとその実装例として以下のものを私は使用しています。
export type Result<T = void, E = void> =
| { ok: true; value: T }
| { ok: false; error: E }
export const Ok = <T, E>(value: T): Result<T, E> => ({
ok: true,
value,
})
export const Err = <T, E>(error: E): Result<T, E> => ({ ok: false, error })
どう活用できるんですか?
エラーの種類に応じて処理を変えたい場合に相性良く活用できます。
例えば、Sign in処理を行う際に、ユーザーのリクエストが不正である場合と、それ以外のネットワーク起因の場合とで処理を使い分けたいときを想定してみます。
Result型を使うと下記のように書くことが出来ます。
// エラーを区別して出す関数
async function signin(payload: SigninPayload): Promise<Result<SigninResponse, "INVALID_REQUEST" | "UNKNOWN_ERROR">> {
try {
const res = await fetch("./login", { method: "POST", body: JSON.stringify(payload) })
if (res.ok) {
return Ok(res.json() as SigninResponse)
}
if (res.status === 400) {
return Err("INVALID_REQUEST")
}
return Err("UNKNOWN_ERROR")
} catch {
return Err("UNKNOWN_ERROR")
}
}
// SomeComponent.tsx
const SomeComponent = () => {
const handleSubmit = () => {
const result = await signin(payload)
if (result.ok) return
if (result.error === "INVALID_REQUEST") {
console.log("パスワードとかが間違っているかも")
}
if (result.error === "UNKNOWN_ERROR") {
console.log("レスポンスがJSONじゃなかったり、ネットワークエラーかも...")
}
}
return <form onSubmit={handleSubmit}>...</form>
}
result.ok
がtrue
であれば、result.value
にアクセスしてその結果を受け取ることができ、そうでない場合はdata.error
にアクセスが可能でその値を元に処理を行えます。
Result型を使わないよくある方法として、try catchのcatchでのエラーハンドリングがある思います。
try {
const result = await signin(payload)
} catch (err) {
if (err instanceof CustomError) {
console.log("独自のErrorクラス定義してハンドリング")
}
if (err instanceof Error) {
console.log("CustomErrorより前にいると引っかかる")
}
}
もちろんこれでも可能ですが、以下のデメリットが存在すると思います。
- catchで掴む値は
any
型なのでちゃんと型安全に行うには、instanceof CustomError
などを使う必要がある - Intellisenseのサポートを得られない。
- 新しくスコープを作ってしまうことや、ネストが深くなる
一方Result型は
- 型安全
- Intellisenseのサポートを得られる
- スコープもネストも深くならない
- 何のエラーが返ってくるかという関心の分離が行える
ちなみにこういったライブラリも既にあるので、興味のある人は導入を検討してみても良いと思います。
(ただ個人的には上記の例程度のもので現状は十分だと感じています。)
よかったら導入を検討してみてください。