如何在fp-ts错误处理函数中访问ReaderTaskEither内的变量?
解决方案
要在错误发生时访问已创建的事务tx并执行回滚,核心是确保事务引用能被传递到错误处理逻辑中。以下是两种可行的实现方式:
方式一:将事务上下文包裹在链式调用中
通过chainW将事务创建与后续业务逻辑绑定,确保事务引用在整个流程中可访问:
import { pipe } from "fp-ts/lib/function"; import { ask, chainW, fromTask, fromTaskEither, foldW, map } from "fp-ts/lib/ReaderTaskEither"; import { Do } from "fp-ts-contrib/lib/Do"; export const AddUserToTeam = ({ userId, teamId }: AddUserToTeamDto) => { return pipe( // 先获取依赖 ask<Deps>(), chainW((deps) => pipe( // 创建事务 fromTask(deps.createTransaction()), chainW((tx) => pipe( Do, // 执行添加用户到团队的业务逻辑 bind("addToTeams", () => fromTaskEither(deps.userRepo.addToTeam(userId, teamId, tx)) ), // 执行事件分发 bindW("result", () => fromTaskEither(deps.dispatcher.create({ userId, teamId })({ tx })) ), // 保留事务和业务结果 map(({ result }) => ({ tx, result })), // 处理成功/失败逻辑 foldW( // 错误分支:回滚事务并返回错误 async (error) => { await tx.rollback(); return left(error); }, // 成功分支:提交事务并返回结果 async ({ tx, result }) => { await tx.commit(); return right(result); } ) ) ) ) ) ); };
逻辑说明
- 先通过
ask<Deps>()获取依赖,再用chainW进入事务创建流程 - 事务创建成功后,所有后续业务逻辑都处于该事务的上下文中,
tx可直接访问 - 若业务逻辑或事件分发失败,
foldW能直接拿到tx执行回滚 - 若事务创建本身失败(比如数据库连接问题),则直接返回错误,无需回滚
方式二:自定义错误类型携带事务引用
如果需要更灵活的错误处理,可以自定义包含事务引用的错误类型,在出错时将事务附加到错误中:
步骤1:定义自定义错误类
class TransactionalError extends Error { constructor( public readonly tx?: Transaction, message: string, public readonly cause: unknown ) { super(message); this.name = "TransactionalError"; } }
步骤2:修改业务逻辑包装错误
export const AddUserToTeam = ({ userId, teamId }: AddUserToTeamDto) => { return pipe( Do, bind("deps", () => ask<Deps>()), bind("tx", ({ deps }) => fromTask(deps.createTransaction())), bind("addToTeams", ({ deps, tx }) => fromTaskEither(deps.userRepo.addToTeam(userId, teamId, tx)).mapLeft( (err) => new TransactionalError(tx, "添加用户到团队失败", err) ) ), bindW("result", ({ deps, tx }) => fromTaskEither(deps.dispatcher.create({ userId, teamId })({ tx })).mapLeft( (err) => new TransactionalError(tx, "分发事件失败", err) ) ), map(({ result }) => result), // 全局错误处理 foldW( async (error) => { // 判断是否包含事务引用,若有则回滚 if (error instanceof TransactionalError && error.tx) { await error.tx.rollback(); } return left(error); }, async (result) => right(result) ) ); };
逻辑说明
- 每个可能失败的业务步骤都将错误包装为
TransactionalError,并携带当前tx引用 - 在最终的
foldW中,通过类型判断获取事务引用并执行回滚 - 若事务创建阶段失败(未进入后续bind步骤),则错误不会包含
tx,无需处理回滚
内容的提问来源于stack exchange,提问作者Adam A
相关产品推荐
相关产品推荐

