You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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);
              }
            )
          )
        )
      )
    )
  );
};

逻辑说明

  1. 先通过ask<Deps>()获取依赖,再用chainW进入事务创建流程
  2. 事务创建成功后,所有后续业务逻辑都处于该事务的上下文中,tx可直接访问
  3. 若业务逻辑或事件分发失败,foldW能直接拿到tx执行回滚
  4. 若事务创建本身失败(比如数据库连接问题),则直接返回错误,无需回滚

方式二:自定义错误类型携带事务引用

如果需要更灵活的错误处理,可以自定义包含事务引用的错误类型,在出错时将事务附加到错误中:

步骤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)
    )
  );
};

逻辑说明

  1. 每个可能失败的业务步骤都将错误包装为TransactionalError,并携带当前tx引用
  2. 在最终的foldW中,通过类型判断获取事务引用并执行回滚
  3. 若事务创建阶段失败(未进入后续bind步骤),则错误不会包含tx,无需处理回滚

内容的提问来源于stack exchange,提问作者Adam A

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 23:27:40