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

fp-ts中pipe内函数未执行问题排查与语法修正求助

fp-ts pipe与TaskEither使用问题排查及修复

问题描述

作为函数编程新手,使用fp-ts的pipe函数时遇到异常:

  • 调用包含tryCatch的validateRegistrationForm函数时,tryCatch、chain或fold的逻辑从未执行,断点直接跳到后续代码
  • 单独调用validateRegistrationForm(user)并打印时,输出的是一个函数而非预期的TaskEither返回值

错误分析

  1. TaskEither的惰性执行特性:TaskEither<E, A>本质是() => Promise<Either<E, A>>,属于惰性异步函数,直接打印会显示函数本身,必须调用它(加())才会触发执行。
  2. Joi验证逻辑错误:schema.validateAsync验证失败时会直接抛出异常,而非返回包含error的对象,原代码中let {error} = await schema.validateAsync(user);的解构完全无效,无法捕获验证错误。
  3. 未执行最终的Task:fold方法处理TaskEither后返回的是Task<void>(即异步函数),原代码中仅调用pipe但未执行这个最终的异步函数,导致整个流程没有实际运行。

修复方案

1. 修正Joi验证错误处理

去掉错误的解构逻辑,让tryCatch直接捕获validateAsync抛出的异常:

// 移除错误的{error}解构,直接等待validateAsync完成
await schema.validateAsync(user);
// 验证通过直接返回user,失败则由tryCatch捕获异常

2. 执行最终的Task流程

在postUser中,pipe执行后得到的是一个异步函数,必须调用它才会触发整个验证、保存的流程:

// 调用pipe返回的异步函数,触发流程
pipe(
  // ... 原有逻辑
)();

3. 理解TaskEither的调用逻辑

如果需要单独测试validateRegistrationForm,需要调用返回的函数并处理Promise:

validateRegistrationForm(user)().then(either => {
  console.log(either); // 此时才能看到Either类型的结果
});

完整修正代码

修正后的validateRegistrationForm

const validateRegistrationForm = (user: User): TaskEither<Error, User> => tryCatch(
  async () => {
    const schema = Joi.object().keys({
      userName: Joi.string().min(4).max(255).required(),
      firstName: Joi.string().pattern(new RegExp(/^[a-z\d\-_\s]+$/i)).min(1).max(50).required(),
      lastName: Joi.string().pattern(new RegExp(/^[a-z\d\-_\s]+$/i)).min(1).max(50).required(),
      emailAddress: Joi.string().email().required(),
      password: Joi.alternatives().try(Joi.string(), Joi.number()).required(),
      confirmPassword: Joi.alternatives().try(Joi.string(), Joi.number()).valid(Joi.ref('password')).required()
      // 原代码用disallow会要求确认密码与原密码不同,不符合注册逻辑,改为valid确保一致
    });

    // 验证失败直接抛出异常,由tryCatch捕获
    await schema.validateAsync(user);

    return user;
  },
  (reason: any) => reason instanceof Error ? reason : new Error(reason?.message || 'Validation failed')
);

修正后的createUser

const createUser = (user: User): TaskEither<Error, User> => {
  return pipe(
    user,    
    validateRegistrationForm, 
    chain((validContent: User) => {
      // 无需重新构造user,直接传入validContent即可
      return repository.saveUser(validContent);
    })
  );
};

修正后的postUser

postUser: (req, res) => {
  // 调用pipe返回的异步函数,触发整个流程
  pipe(
    req.body,
    interactor.createUser,
    fold(
      (error) => async () => {
        console.error(error);
        res.status(400).send({ error: error.message }); // 验证错误用400状态码更合理
      },
      (user) => async () => {
        res.status(201).send(user); // 创建成功用201状态码更规范
      }
    )
  )();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 23:35:27