
Effect v4 错误处理迁移指南catch*组合子重命名与catchReason/catchEager新能力【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code在 Effect 从 v3 升级到 v4 的众多破坏性变更中错误处理Error Handling相关组合子的重命名是最常见、也最容易在迁移时遗漏的一类。本指南以 effect-smol 仓库中 error-handling.md 迁移文档为核心完整梳理Effect.catch*系列 API 的 v3→v4 映射关系逐条给出可复制的迁移前后代码并结合 v4 源码Effect.ts、Filter.ts剖析catchFilter、catchReason、catchEager等新旧 API 的底层语义。读完本文你将能够无痛完成项目中的catch*重命名迁移并掌握 v4 新增的嵌套错误原因处理与同步恢复优化能力。一、为什么 v4 要对catch*组合子做全面重命名v3 时代的错误处理 API 命名不够一致既有catchAll*catchAll、catchAllCause、catchAllDefect又有catchSome*catchSome、catchSomeCause、catchSomeDefect还有保留不变的catchTag/catchTags/catchIf。v4 将命名收敛为一条统一规则catchAll*系列缩短为catch*All后缀被去掉因为“捕获全部错误”本来就是这些组合子的默认语义catchSome*系列被catchFilter/catchCauseFilter取代v4 用全新的Filter模块替代了基于Option的“选择性捕获”写法语义更清晰、可组合性更强catchSomeDefect被直接移除选择性捕获缺陷defect在 v4 中不再保留需要改为其他手段处理。从源码看这些 API 的文档注释均标注since 4.0.0如 Effect.ts 中catch_ as catch的导出以及catchCause、catchFilter、catchReason等定义实现则集中在 internal/effect.ts其中可见catch_、catchCause、catchCauseFilter、catchFilter、catchTag、catchReason、catchReasons、catchEager等一组内部实现。effect-smol 仓库中的 effect 包当前版本为4.0.0-rc.112见 package.json本文所有示例均以此为准。二、重命名总览v3 → v4 完整对照表以下是迁移文档给出的完整映射关系建议在迁移时以此表为检查清单v3v4Effect.catchAllEffect.catchEffect.catchAllCauseEffect.catchCauseEffect.catchAllDefectEffect.catchDefectEffect.catchTagEffect.catchTag不变Effect.catchTagsEffect.catchTags不变Effect.catchIfEffect.catchIf不变Effect.catchSomeEffect.catchFilterEffect.catchSomeCauseEffect.catchCauseFilterEffect.catchSomeDefect已移除Removed可以看出纯重命名的只有catchAll、catchAllCause、catchAllDefect三个通常可以机械替换语义与用法同时改变的是catchSome、catchSomeCause而catchSomeDefect需要寻找替代方案。下面逐项展开。三、Effect.catchAll→Effect.catch这是最直接的一对重命名函数签名与行为完全一致捕获所有可恢复错误typed error用提供的恢复 Effect 兜底但不会捕获缺陷defect——v4 源码文档明确指出catch只处理可恢复错误不可恢复的缺陷保持原样见 Effect.ts。v3 写法import { Effect } from effect const program Effect.fail(error).pipe( Effect.catchAll((error) Effect.succeed(recovered: ${error})) )v4 写法import { Effect } from effect const program Effect.fail(error).pipe( Effect.catch((error) Effect.succeed(recovered: ${error})) )在 t3code 仓库中Effect.catch的 v4 风格用法已经大量落地。典型模式是“捕获失败并降级为日志警告”例如 packages/client-runtime/src/connection/registry.ts 中保存平台凭据失败时记录警告而不中断流程yield* credentials.put(registration.target.connectionId, registration.credential).pipe( Effect.catch((error) Effect.logWarning(Could not store the platform bearer credential., { environmentId: target.environmentId, error, }), ), );同样的模式也出现在 packages/client-runtime/src/state/server.ts读取缓存配置失败降级为Option.none与 packages/client-runtime/src/relay/discovery.ts刷新账户信息失败后把错误写入状态等处。如果你在旧代码里看到Effect.catchAll(...)直接改名即可无需改动回调逻辑。四、Effect.catchAllCause→Effect.catchCausecatchCause接收整个Cause失败原因树因此既能恢复可恢复错误也能恢复缺陷与中断——它比catch的覆盖面更大。v4 中Cause数据结构本身也发生了扁平化重构见 cause.md这里先看重命名示例。v3 写法import { Effect } from effect const program Effect.die(defect).pipe( Effect.catchAllCause((cause) Effect.succeed(recovered)) )v4 写法import { Cause, Effect } from effect const program Effect.die(defect).pipe( Effect.catchCause((cause) Effect.succeed(recovered)) )t3code 中catchCause的实战用法体现了“检查 Cause 后再决定是否恢复”的精细控制。例如 packages/client-runtime/src/state/pullRequestRouting.ts 中对 PR 路由请求设置 2 秒超时并区分“被中断”与“普通失败”const identity yield* request(WS_METHODS.pullRequestsRouting, ref).pipe( Effect.timeout(2 seconds), Effect.catchCause((cause) Cause.hasInterrupts(cause) ? Effect.interrupt : Effect.succeed(null), ), );这里用到了Cause.hasInterrupts(cause)这一 v4 的 Cause 级谓词v3 中的Cause.isInterrupted已对应改名为Cause.hasInterrupts详见 cause.md 中的谓词对照表。此外packages/shared/src/KeyedCoalescingWorker.ts 使用Effect.catchCause在任意原因含缺陷下执行清理逻辑流式场景中 packages/client-runtime/src/rpc/client.ts 与 packages/client-runtime/src/rpc/session.ts 则使用Stream.catchCause处理流错误。这些都是在迁移后按 v4 语义编写或改造的代码。五、Effect.catchAllDefect→Effect.catchDefectcatchDefect专门处理非预期的缺陷如抛出的异常、断言失败是三个纯重命名中最少见的一类。v4 源码文档示例Effect.tsimport { Effect } from effect const program Effect.die(Something went wrong) const recovered Effect.catchDefect(program, (defect) { // defect: unknown —— 缺陷本身不是类型化错误 return Effect.succeed(Recovered from defect: ${String(defect)}) })迁移注意catchDefect的回调参数类型是unknownv3 中同样是unknown返回的 Effect 类型为EffectA | A2, E | E2, R | R2即恢复路径可以引入新的错误类型。缺陷通常意味着严重问题v4 文档建议仅在极少数场景如动态加载插件下做受控恢复。六、不变的组合子catchTag/catchTags/catchIf这三个 API 在 v4 中名称与用法均保持不变迁移时无需改动catchTag(TagName, handler)按_tag字段精确匹配单个标记错误catchTags({ TagName: handler, ... })按_tag一次匹配多个标记错误catchIf(predicate, handler, orElse?)按谓词Predicate或类型收窄Refinement条件匹配错误v4 中还支持可选的orElse处理未匹配分支见 Effect.ts。t3code 中 packages/shared/src/relayClient.ts 的实例展示了catchTag的典型用法——捕获PlatformError并转换为领域错误继续传播yield* acquireInstallLock(lockPath).pipe( Effect.catchTag(PlatformError, (cause) Effect.fail( new RelayClientInstallError({ reason: write_failed, message: Could not acquire the relay client installation lock., cause, }), ), ), );七、Effect.catchSome→Effect.catchFilter从 Option 到 Filter 模块这是本次重命名中用法变化最大的一对不只是改名连捕获条件的表达方式都换了v3 的catchSome要求回调返回OptionEffectSome表示捕获并恢复None表示放行v4 的catchFilter改为接收一个Filter作为第一个参数谓词阶段第二个参数才是恢复函数。v3 写法import { Effect, Option } from effect const program Effect.fail(42).pipe( Effect.catchSome((error) error 42 ? Option.some(Effect.succeed(caught)) : Option.none() ) )v4 写法import { Effect, Filter } from effect const program Effect.fail(42).pipe( Effect.catchFilter( Filter.fromPredicate((error: number) error 42), (error) Effect.succeed(caught) ) )7.1Filter模块的核心模型v4 把“选择性捕获”的条件抽象成了独立的Filter模块Filter.ts。其核心类型是一个函数export interface Filterin Input, out Pass Input, out Fail Input { (input: Input): Result.ResultPass, Fail }即Filter接收输入返回一个Result——成功表示“通过过滤”可携带收窄或转换后的值失败表示“被过滤掉”。这正是catchFilter内部判定逻辑的基石过滤器作用于从Cause中提取的类型化错误Result成功的结果交给恢复函数f失败的结果在有orElse时交给orElse没有orElse则保留原始失败 Cause见 Effect.ts 的catchFilter文档。Filter模块提供了丰富的构造器与组合器常用 API 如下API说明Filter.fromPredicate(predicate)由谓词/Refinement 构造通过则Result.succeed(input)否则Result.fail(input)Filter.fromPredicateOption(f)由返回Option的函数构造Some(v)通过并携带vNone失败Filter.make(f)由返回Result的纯函数直接构造Filter.makeEffect(f)构造可进行异步/依赖注入的“效应式”过滤器Filter.tagged(Tag)按_tag匹配标记错误的预置过滤器Filter.string/Filter.number等内置的常见值类型过滤器Filter.try(f)尝试执行函数抛异常则返回failFilter.or(left, right)组合两个过滤器逻辑或Filter.zip(a, b)按序组合两个过滤器Filter.mapFail(f)转换过滤失败值Filter.toPredicate(self)转回布尔谓词便于复用由于Filter本身是可组合的or、zip、mapFail等v4 的选择性错误捕获比 v3 的Option回调更利于声明式复用。例如Effect.catchFilter(Filter.tagged(NotFound), handler)可以等价替代 v3 中按_tag手写条件匹配的catchSome。7.2catchFilter的类型语义catchFilter的重载签名见 Effect.ts 附近支持“先过滤器后处理函数”与“先 self 后过滤器”两种柯里化调用方式恢复函数的参数类型由过滤器通过后的类型自动收窄配合orElse还能对未匹配分支做二次处理。这是 v3catchSome所不具备的类型安全提升。八、Effect.catchSomeCause→Effect.catchCauseFilter与catchFilter相对应catchCauseFilter的过滤器作用对象不是类型化错误而是完整的Cause。过滤器成功时恢复函数会同时收到“选中的值”与“原始 Cause”过滤器失败时Effect 以过滤器返回的残留 Cause 重新失败见 Effect.tsimport { Cause, Effect, Filter } from effect const program Effect.fail(network error) const recovered Effect.catchCauseFilter( program, // 过滤器作用于整个 Cause这里选择包含可恢复失败的 Cause Filter.fromPredicate((cause: Cause.Causestring) Cause.hasFails(cause)), (failure, cause) Effect.succeed(recovered: ${failure}) )顺带一提v4 还提供了谓词版的catchCauseIf(predicate, f)Effect.ts它接收普通谓词而非Filter适合不需要转换值的简单 Cause 选择场景。三者catchCause、catchCauseFilter、catchCauseIf按“全部捕获 / Filter 捕获 / 谓词捕获”三档覆盖了 Cause 级别的恢复需求。九、Effect.catchSomeDefect已被移除catchSomeDefect在 v4 中没有直接替代品。v3 中它用于按条件选择性捕获缺陷v4 的设计哲学是缺陷属于“非预期错误”不应被条件化地静默吞噬。如果迁移时遇到catchSomeDefect请评估具体场景若确实需要处理全部缺陷 → 改用Effect.catchDefect若只是想在缺陷发生时记录日志并放行 → 使用Effect.tapErrorCause或Effect.onError副作用观察不改写错误通道后让缺陷继续向上传播若想区分缺陷类型再决定是否恢复 → 在catchCause/catchCauseFilter内部用Cause.hasDies(cause)、Cause.findDefect(cause)等 v4 Cause 工具做判断见 cause.md。十、v4 新增能力catchReason与catchReasons除了重命名v4 还引入了针对**嵌套错误原因nested reason**的捕获能力这是本次迁移文档中“New in v4”部分的重点。10.1Effect.catchReason(errorTag, reasonTag, handler)业务中常见“父错误包裹子原因”的错误结构例如 AI 调用错误AiError内部再携带reason: RateLimitError | QuotaExceededError。v3 要处理这种情况必须层层解包v4 的catchReason可以直接针对父错误内的某个具体 reason 标记进行捕获并且不把父错误从错误通道中移除未被匹配的 reason 时父错误依然保留。v4 源码中的完整示例Effect.tsimport { Data, Effect } from effect class RateLimitError extends Data.TaggedError(RateLimitError){ retryAfter: number } {} class QuotaExceededError extends Data.TaggedError(QuotaExceededError){ limit: number } {} class AiError extends Data.TaggedError(AiError){ reason: RateLimitError | QuotaExceededError } {} const program: Effect.Effectstring, AiError Effect.fail( new AiError({ reason: new RateLimitError({ retryAfter: 30 }) }) ) // 只处理限流这一个 reason其余 reason 保留为 AiError const handled program.pipe( Effect.catchReason(AiError, RateLimitError, (reason) Effect.succeed(Retry after ${reason.retryAfter}s) ) ) Effect.runSync(handled) // Retry after 30s从签名Effect.ts看catchReason还支持可选的第四个参数orElse当父错误内的 reason 未命中reasonTag时orElse会收到剩余 reasons 与剔除后的父错误可用于兜底恢复不传orElse时未命中的父错误会原样保留在错误通道中类型系统也会据此把ExcludeTagE, K与ExtractTagE, K精确地组合进最终错误类型。10.2Effect.catchReasons(errorTag, cases)当需要在一个父错误内同时处理多个 reason 时catchReasons用一个“标记 → 处理器”的对象一次搞定Effect.tsimport { Data, Effect } from effect class RateLimitError extends Data.TaggedError(RateLimitError){ retryAfter: number } {} class QuotaExceededError extends Data.TaggedError(QuotaExceededError){ limit: number } {} class AiError extends Data.TaggedError(AiError){ reason: RateLimitError | QuotaExceededError } {} const program: Effect.Effectstring, AiError Effect.fail( new AiError({ reason: new QuotaExceededError({ limit: 100 }) }) ) const handled program.pipe( Effect.catchReasons(AiError, { RateLimitError: (reason) Effect.succeed(Rate limited: ${reason.retryAfter}s), QuotaExceededError: (reason) Effect.succeed(Quota exceeded: ${reason.limit}), }) )cases中每个 handler 的参数类型按对应 reason 自动收窄同样支持可选的orElse处理未覆盖的 reason。与catchReason配套的还有一个方向相反的 APIEffect.unwrapReason(AiError)Effect.ts它把嵌套 reason“提升”到 Effect 错误通道顶层Effectstring, AiError→Effectstring, RateLimitError | QuotaExceededError适用于希望把嵌套错误扁平化后再统一处理的场景。十一、v4 新增能力Effect.catchEager同步立即恢复catchEager是catch的优化变体当错误在构造阶段就已同步确定“已决失败”时恢复函数会立即同步执行避免额外的一次异步调度对于成功 Effect 原样放行对于尚未决pending如经过delay等操作的 Effect则退化为普通catch行为见 Effect.ts 的文档与示例import { Effect } from effect // 已决失败恢复函数立即执行 const failed Effect.fail(original error) const recovered Effect.catchEager( failed, (err: string) Effect.succeed(recovered from: ${err}) ) // 成功原样放行 const success Effect.succeed(42) const unchanged Effect.catchEager(success, (err: string) Effect.succeed(...)) // 挂起中的 Effect与普通 catch 行为一致 const pending Effect.delay(Effect.fail(error), 0) const recoveredPending Effect.catchEager(pending, (err: string) Effect.succeed(...))签名Effect.ts与catch一致(f: (e) EffectB, E2, R2) ...返回EffectA | B, E2, R | R2。使用建议当恢复路径是纯同步构造不依赖真实异步 IO时优先使用catchEager换取低开销需要与运行时异步行为严格对齐时仍使用catch。十二、迁移检查清单结合本文内容给出catch*系列迁移的自检清单全局搜索catchAll(→ 改为catch(逻辑不变全局搜索catchAllCause(→ 改为catchCause(回调参数仍为Cause但注意 v4 的Cause已是扁平化结构reasons数组若回调里对Cause做了模式匹配需同步参照 cause.md 改造全局搜索catchAllDefect(→ 改为catchDefect(全局搜索catchSome(→ 改写为catchFilter(Filter.fromPredicate(...), handler)或使用Filter.tagged/Filter.or等组合器全局搜索catchSomeCause(→ 改写为catchCauseFilter(Filter..., handler)全局搜索catchSomeDefect(→ 按第九节评估替代方案catchTag/catchTags/catchIf无需改动但可考虑为catchIf增加orElse处理未匹配分支若项目使用Option作为选择性捕获的载体注意 v4 中Effect的Option返回值相关 API 也已迁移至Result模块见 v3-to-v4.md 中的effect/Either - effect/Result映射。十三、总结Effect v4 对catch*组合子的整理遵循“收敛命名、强化类型、统一谓词抽象”三个方向catchAll*纯重命名为catch*catchSome*升级为基于Filter模块的catchFilter/catchCauseFilter获得类型收窄与可组合性catchReason/catchReasons则补齐了嵌套错误原因的精准处理能力catchEager为已决失败提供了同步恢复的优化路径。迁移时以第二节对照表为骨架配合本文的源码级语义解析与 t3code 仓库中 registry.ts、pullRequestRouting.ts、relayClient.ts 等落地案例即可安全、快速地完成错误处理代码的 v3→v4 迁移。【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考