Next.js + サーバーサイドTypeScript + 関数フレーバーでクリーンなアプリを作ったので実装意図とか書く Advent Calendar 2022
の8日目。株式会社mofmofに生息しているshwldです。
前日はEither型について書きました
TaskEither型を使ってResult型を作る
fp-tsにはTaskEitherというものがあります。
失敗する可能性のある(Either)非同期処理(Task)を表現することができるものです。
具体的にみていきましょう。
fp-tsのtryCatchを使うことで、PromiseをTaskEitherに変換することができます。
import { tryCatch } from 'fp-ts/TaskEither'
const taskEither = tryCatch(() => {
saveToDatabase()
}, e => {
if (e instanceof PrismaClientRustPanicError) {
return new OriginalDBError(e);
}
return new OriginalRuntimeError('error');
}
上記で得られる taskEither
は実行(taskEither()
)するとPromise<Either<left, right>>
が返ってきます。
嬉しいこと
Eitherの利点については昨日書いたので、ここではTaskの利点について説明します。
Taskを使うことで、複数のPromiseの直列実行、並列実行を操作し一つのPromiseにまとめて、Eitherの配列にしてくれます。
import { sequenceArray } from 'fp-ts/TaskEither'
const taskEithers = items.map(item => saveToDatabase);
// taskEithers is TaskEither<OriginalDBError, DBResult>[]
const taskEither = sequenceArray(taskEithers)
// taskEither is TaskEither<OriginalDBError, DBResult[]>
const results = await taskEither()
// results is Either<OriginalDBError, DBResult[]>
一つのPromiseにまとめてくれることにより、取り出す際に一つ一つawaitしたりすることがなくなるのはもちろん、取り出す以前の状態で、直列、並列実行を指定でき、宣言的に非同期処理を扱えて嬉しい。
EitherとTaskEitherを区別するのがだるい
EitherとTaskEitherを使えるようになりました。
fp-tsには他にもOptionやIOなど連続した処理の流れの中で例外を管理するような仕組みがいくつもあります。
TaskEitherをResult型として使う
今回自分のテーマとして、関数型を知らない人でも書けるといいなと言う思いがありました。
そこで、選択したのがResult型として、TaskEitherのみを使うという選択です。
EitherをすべてTaskEitherに変換することで、すべての処理の連続の中でResult型だけを気にすれば良くなりました(これがいいのかはわかりませんが)
つまり、すべてPromiseとして扱うということになり、そこは少し抵抗があったのですが、同期でないとやりづらいところはsuffixにSync
をつけたバージョンのメソッドを生やすようにしました。
コードをみるとわかりますが、Result型はいたるところに出てきます。
export const build = (
input: Account_BuildInput
): Result<InvalidAttributesError, Account_BuiltAttributes> => {
return pipe(
{
...input,
createdAt: new Date(),
updatedAt: new Date(),
},
validateWith(validationSchema),
map(v => ({
...v,
__state: STATE_IS_BUILT,
}))
);
};
次回予告
明日はエンティティの状態遷移関数について書きます。