I have seen many examples of using TaskEither to say make an hhtp request or read a file etc.
What I am trying to do is simulate finding item in a DB by ID, so possible outputs of the operation could be:

  1. 找到了那件物品
  2. 找不到ID为的项目
  3. 某些错误(即数据库连接)

这样的界面应该是TaskEither<Error, Option<A>>:

type TaskEO<A> = TaskEither<Error, Option<A>>

由于我将以HTTP响应的形式发送结果(对GET查询的响应),因此我希望能够清楚地区分上述3种场景.每种类型的响应代码为:

  1. 200多个有效载荷
  2. 404
  3. 500

我已经编写了以下代码,将3种可能的场景处理为相应的HTTP响应:

import * as O from "fp-ts/Option";
import * as TE from "fp-ts/TaskEither";
import * as E from "fp-ts/Either";
import { pipe } from "fp-ts/function";

type TaskEO<A> = TE.TaskEither<Error, O.Option<A>>;

const getGoodStuff = (id: string): TaskEO<string> => TE.of(O.some(`result for ${id}`));

const getBadStuff = (id: string): TaskEO<string> =>
  TE.left(new Error(`failed fetching ${id}`));

const getEmptyStuff = (id: string): TaskEO<string> => TE.of(O.none);

getGoodStuff("123")()
  .then((e) =>
    pipe(
      e,
      E.fold(
        (error) => `500: Internal Server Error`,
        (stuff) =>
          pipe(
            stuff,
            O.match(
              () => `404: Not Found Error`,
              (value) => `200: Yay we got: "${value}"`
            )
          )
      )
    )
  )
  .then(console.log);

您可以将getGoodStuff调用替换为其他get...Stuff个函数中的任何一个,以查看它确实正确地处理了不同的响应!很棒的东西!

However, and here is the question for YOU dear reader, I have a feeling that there is a smarter way of structuring this composition. Unfortunately my knowledge of fp-ts is still somewhat limited.
How would YOU write the code above?
Thanks x


EDIT个 我把范围缩小到这样的范围:

enum HttpResponseCode {
  OK = 200,
  NOT_FOUND = 404,
  INTERNAL_SERVER_ERROR = 500
}

type HttpResponse = {
  code: HttpResponseCode;
  payload: unknown;
}

const toHttpResponse = <A>(e: E.Either<Error, O.Option<A>>): HttpResponse =>
  E.fold(
    (error) => ({ code: HttpResponseCode.INTERNAL_SERVER_ERROR, payload: "Internal Server Error" }),
    O.match(
      () => ({ code: HttpResponseCode.NOT_FOUND, payload: "Resource not found" }),
      (value) => ({ code: HttpResponseCode.OK, payload: value })
    )
  )(e)

然后可以通过以下方式与Express路由处理程序一起使用:

async (req, res) => {
      await findStuffById(req.params.stuffId)()
        .then(toHttpResponse)
        .then(({ code, payload }) => res.status(code).send(payload))
    }

推荐答案

我认为,你在编辑中得到的东西是最干净的.你想要在所有可能的情况下都做一些事情,所以folding或matching是工作的工具.

如果您发现需要经常匹配此形状,并且编写的函数体始终相同,则可以考虑编写一个帮助程序,如下所示:

function matchTaskEO<A, R>({
  onError,
  onNone,
  onSome,
}: {
  // Up to you if you want to name them or use positional arguments
  // FP-TS opts for the latter but I find this easier to parse personally.
  onError: (e: Error) => R,
  onNone: () => R,
  onSome: (a: A) => R,
}) {
  return (taskEO: TaskEO<A>) => E.match(
    onError,
    O.match(onNone, onSome),
  );
}

然后,您可以使用它来实现toHttpResponse:

const toHttpResponse = <A>(taskEO: TaskEO<A>) => matchTaskEO<A, HttpResponse>({
  onError: (e) => ({ 
    code: HttpResponseCode.INTERNAL_SERVER_ERROR,
    payload: "Internal Server Error",
  }),
  onNone: () => ({
    code: HttpResponseCode.NOT_FOUND,
    payload: "Resource not found",
  }),
  onSome: (value) => ({ code: HttpResponseCode.OK, payload: value })
})(taskEO);

这使定义变得平坦,尽管在这种情况下编写显式的EitherOption匹配看起来并不是太不清楚.

Typescript相关问答推荐

如何使用TypScript和react-hook-form在Next.js 14中创建通用表单组件?

TypeScript将键传递给新对象错误

如何在第一次加载组件时加载ref数组

如何在排版中正确键入中间件链和控制器链

创建一个根据布尔参数返回类型的异步函数

有没有办法解决这个问题,类型和IntrinsicAttributes类型没有相同的属性?

material 表不渲染任何数据

与字符串文字模板的结果类型匹配的打字脚本

如何在另一个参数类型中获取一个参数字段的类型?

TypeScrip原始字符串没有方法

Material UI / MUI系统:我如何告诉TypeScript主题是由提供程序传递的?

在实现自定义主题后,Angular 不会更新视图

使用Redux Saga操作通道对操作进行排序不起作用

是否将自动导入样式从`~/目录/文件`改为`@/目录/文件`?

如何在Typescript 中组合unions ?

接口中可选嵌套属性的类型判断

如何提取具有索引签名的类型中定义的键

在Typescript 无法正常工作的 Three.js 中更新动画中的 GLSL 统一变量

基于泛型类型的条件类型,具有条目属性判断

使用react+ts通过props传递数据