エラーをmapErrorで組み替えたりするが、それのやり方の比較確認のためのメモ
改善案は自動で推論してくれないので、微妙な気がしてき
前回
共通部
カスタムエラークラスやログ処理の共通クラス
class SleepNGBeforeError {
static tagName = 'Sleep.Ng.Before'
readonly _tag = SleepNGBeforeError.tagName
static isClass(test: any): test is SleepNGBeforeError {
return test._tag === SleepNGBeforeError.tagName
}
getMessage(site: string) {
return `${site} - ng - Sleep NG before wait`
}
}
class SleepNGAfterError {
static tagName = 'Sleep.Ng.After'
readonly _tag = SleepNGAfterError.tagName
wait: number
constructor(w: number) {
this.wait = w
}
static isClass(test: any): test is SleepNGAfterError {
return test._tag === SleepNGAfterError.tagName
}
getMessage(site: string) {
return `${site} - ng - ${this.wait} - Sleep NG after wait`
}
}
class OkData {
wait: number
constructor(w: number) {
this.wait = w
}
static isClass(test: any): test is OkData {
return test instanceof OkData
}
static getLastIndex(site: string) {
const index = site.slice(-1)
return Number(index)
}
getMessage(site: string) {
return `${site} - ok - ${this.wait}`
}
}
const checkLog = (text: string) =>
Effect.sync(() => console.log(`time : ${new Date().toISOString()} - text: ${text}`))
冗長に感じるコード
いかのコードが長く感じる、err.errorが_tagを持っているかだけの判断をしたいだけだったが、
型情報を持たせたりする必要があるかどうかわからず、こうなってしまった。
function old_mapError_sleepNg<T>(effectAndThen: Effect.Effect<T, UnknownException, never>) {
const effectAll = Effect.mapError(effectAndThen, (err) => {
if (SleepNGBeforeError.isClass(err.error)) {
return err.error
} else if (SleepNGAfterError.isClass(err.error)) {
return err.error
} else {
Effect.runPromise(checkLog(`mapError_sleepNg : UnknownException : ${err}`))
return err
}
})
return effectAll
}
上記の場合の型情報は以下
function old_mapError_sleepNg<T>(effectAndThen: Effect.Effect<T, UnknownException, never>): Effect.Effect<T, UnknownException | SleepNGBeforeError | SleepNGAfterError, never>
UnknownException | SleepNGBeforeError | SleepNGAfterError がエラーとして取得できる
改善案1 型情報を捨てる場合
型情報をすてて、単純にerr.errorを巻き上げたいだけの場合
function liftMapTags<A, R>(self: Effect.Effect<A, UnknownException, R>) {
function hasTag(err: unknown) {
return Predicate.hasProperty(err, '_tag') && Predicate.isString(err['_tag'])
}
return Effect.mapError(self, (error) => {
if (hasTag(error.error)) {
return error.error
}
Effect.runPromise(checkLog(`liftMapTags : UnknownException : ${error}`))
return error
})
}
function new1_mapError_sleepNg<T>(effectAndThen: Effect.Effect<T, UnknownException, never>) {
const effectAll = liftMapTags(effectAndThen)
return effectAll
}
上記の場合の型情報は以下
function new1_mapError_sleepNg<T>(effectAndThen: Effect.Effect<T, UnknownException, never>): Effect.Effect<T, unknown, never>
unknown がエラーとして取得できる
改善案2 型情報を捨てない場合
function liftMapTagsWithType<A, R, ErrorType extends { tagName: string }>(
self: Effect.Effect<A, UnknownException, R>,
tags: ErrorType[]
) {
function isClass<ErrorType extends { tagName: string }>(
tag: ErrorType,
err: unknown
): err is ErrorType {
return (
Predicate.hasProperty(err, '_tag') &&
Predicate.isString(err['_tag']) &&
tag.tagName === err['_tag']
)
}
return Effect.mapError(self, (error) => {
for (const tag of tags) {
if (isClass(tag, error.error)) {
return error.error
}
}
Effect.runPromise(checkLog(`liftMapTagsWithType : UnknownException : ${error}`))
return error
})
}
function new2_mapError_sleepNg<T>(effectAndThen: Effect.Effect<T, UnknownException, never>) {
const effectAll = liftMapTagsWithType(effectAndThen, [SleepNGBeforeError, SleepNGAfterError])
return effectAll
}
上記の場合の型情報は以下
function new2_mapError_sleepNg<T>(effectAndThen: Effect.Effect<T, UnknownException, never>): Effect.Effect<T, UnknownException | typeof SleepNGBeforeError | typeof SleepNGAfterError, never>
UnknownException | typeof SleepNGBeforeError | typeof SleepNGAfterError がエラーとして取得できる
実行確認
import { describe, test } from 'vitest'
import { Effect, Predicate } from 'effect'
import { UnknownException } from 'effect/Cause'
class SleepNGBeforeError {
static tagName = 'Sleep.Ng.Before'
readonly _tag = SleepNGBeforeError.tagName
static isClass(test: any): test is SleepNGBeforeError {
return test._tag === SleepNGBeforeError.tagName
}
getMessage(site: string) {
return `${site} - ng - Sleep NG before wait`
}
}
class SleepNGAfterError {
static tagName = 'Sleep.Ng.After'
readonly _tag = SleepNGAfterError.tagName
wait: number
constructor(w: number) {
this.wait = w
}
static isClass(test: any): test is SleepNGAfterError {
return test._tag === SleepNGAfterError.tagName
}
getMessage(site: string) {
return `${site} - ng - ${this.wait} - Sleep NG after wait`
}
}
class OkData {
wait: number
constructor(w: number) {
this.wait = w
}
static isClass(test: any): test is OkData {
return test instanceof OkData
}
static getLastIndex(site: string) {
const index = site.slice(-1)
return Number(index)
}
getMessage(site: string) {
return `${site} - ok - ${this.wait}`
}
}
const checkLog = (text: string) =>
Effect.sync(() => console.log(`time : ${new Date().toISOString()} - text: ${text}`))
const testOptions = {
timeout: 1000 * 100
}
function old_mapError_sleepNg<T>(effectAndThen: Effect.Effect<T, UnknownException, never>) {
const effectAll = Effect.mapError(effectAndThen, (err) => {
if (SleepNGBeforeError.isClass(err.error)) {
return err.error
} else if (SleepNGAfterError.isClass(err.error)) {
return err.error
} else {
Effect.runPromise(checkLog(`mapError_sleepNg : UnknownException : ${err}`))
return err
}
})
return effectAll
}
function new1_mapError_sleepNg<T>(effectAndThen: Effect.Effect<T, UnknownException, never>) {
const effectAll = liftMapTags(effectAndThen)
return effectAll
}
function new2_mapError_sleepNg<T>(effectAndThen: Effect.Effect<T, UnknownException, never>) {
const effectAll = liftMapTagsWithType(effectAndThen, [SleepNGBeforeError, SleepNGAfterError])
return effectAll
}
function liftMapTagsWithType<A, R, ErrorType extends { tagName: string }>(
self: Effect.Effect<A, UnknownException, R>,
tags: ErrorType[]
) {
function isClass<ErrorType extends { tagName: string }>(
tag: ErrorType,
err: unknown
): err is ErrorType {
return (
Predicate.hasProperty(err, '_tag') &&
Predicate.isString(err['_tag']) &&
tag.tagName === err['_tag']
)
}
return Effect.mapError(self, (error) => {
for (const tag of tags) {
if (isClass(tag, error.error)) {
return error.error
}
}
Effect.runPromise(checkLog(`liftMapTagsWithType : UnknownException : ${error}`))
return error
})
}
function liftMapTags<A, R>(self: Effect.Effect<A, UnknownException, R>) {
function hasTag(err: unknown) {
return Predicate.hasProperty(err, '_tag') && Predicate.isString(err['_tag'])
}
return Effect.mapError(self, (error) => {
if (hasTag(error.error)) {
return error.error
}
Effect.runPromise(checkLog(`liftMapTags : UnknownException : ${error}`))
return error
})
}
describe('effect-ts Error対応の簡素化', () => {
function oldNew<A>(sleepEffect: Effect.Effect<A, UnknownException, never>) {
const oldEffectAll = old_mapError_sleepNg(sleepEffect)
const old = Effect.runSyncExit(oldEffectAll)
console.log(`old - ${old}`)
const new1EffectAll = new1_mapError_sleepNg(sleepEffect)
const _new1 = Effect.runSyncExit(new1EffectAll)
console.log(`new:1 - ${_new1}`)
const new2EffectAll = new2_mapError_sleepNg(sleepEffect)
const _new2 = Effect.runSyncExit(new2EffectAll)
console.log(`new:2 - ${_new2}`)
}
test('使い方1 - succeed', testOptions, async () => {
const sleepEffect = Effect.succeed(new OkData(123))
oldNew(sleepEffect)
})
test('使い方2 - fail - SleepNGBeforeError', testOptions, async () => {
const sleepEffect = Effect.fail(new UnknownException(new SleepNGBeforeError()))
oldNew(sleepEffect)
})
test('使い方3 - fail - SleepNGAfterError', testOptions, async () => {
const sleepEffect = Effect.fail(new UnknownException(new SleepNGAfterError(123)))
oldNew(sleepEffect)
})
test('使い方4 - fail - UnknownException', testOptions, async () => {
const sleepEffect = Effect.fail(new UnknownException(new Error()))
oldNew(sleepEffect)
})
})
使い方1
old - {
"_id": "Exit",
"_tag": "Success",
"value": {
"wait": 123
}
}
new:1 - {
"_id": "Exit",
"_tag": "Success",
"value": {
"wait": 123
}
}
new:2 - {
"_id": "Exit",
"_tag": "Success",
"value": {
"wait": 123
}
}
使い方2
old - {
"_id": "Exit",
"_tag": "Failure",
"cause": {
"_id": "Cause",
"_tag": "Fail",
"failure": {
"_tag": "Sleep.Ng.Before"
}
}
}
new:1 - {
"_id": "Exit",
"_tag": "Failure",
"cause": {
"_id": "Cause",
"_tag": "Fail",
"failure": {
"_tag": "Sleep.Ng.Before"
}
}
}
new:2 - {
"_id": "Exit",
"_tag": "Failure",
"cause": {
"_id": "Cause",
"_tag": "Fail",
"failure": {
"_tag": "Sleep.Ng.Before"
}
}
}
使い方3
old - {
"_id": "Exit",
"_tag": "Failure",
"cause": {
"_id": "Cause",
"_tag": "Fail",
"failure": {
"_tag": "Sleep.Ng.After",
"wait": 123
}
}
}
new:1 - {
"_id": "Exit",
"_tag": "Failure",
"cause": {
"_id": "Cause",
"_tag": "Fail",
"failure": {
"_tag": "Sleep.Ng.After",
"wait": 123
}
}
}
new:2 - {
"_id": "Exit",
"_tag": "Failure",
"cause": {
"_id": "Cause",
"_tag": "Fail",
"failure": {
"_tag": "Sleep.Ng.After",
"wait": 123
}
}
}
使い方4
time : 2025-02-27T12:47:45.648Z - text: mapError_sleepNg : UnknownException : UnknownException: An unknown error occurred
old - {
"_id": "Exit",
"_tag": "Failure",
"cause": {
"_id": "Cause",
"_tag": "Fail",
"failure": {
"_tag": "UnknownException",
"error": {},
"message": "An unknown error occurred",
"cause": {}
}
}
}
time : 2025-02-27T12:47:45.652Z - text: liftMapTags : UnknownException : UnknownException: An unknown error occurred
new:1 - {
"_id": "Exit",
"_tag": "Failure",
"cause": {
"_id": "Cause",
"_tag": "Fail",
"failure": {
"_tag": "UnknownException",
"error": {},
"message": "An unknown error occurred",
"cause": {}
}
}
}
time : 2025-02-27T12:47:45.652Z - text: liftMapTagsWithType : UnknownException : UnknownException: An unknown error occurred
new:2 - {
"_id": "Exit",
"_tag": "Failure",
"cause": {
"_id": "Cause",
"_tag": "Fail",
"failure": {
"_tag": "UnknownException",
"error": {},
"message": "An unknown error occurred",
"cause": {}
}
}
}