fp-ts 使用 TaskEither 包装选项的最佳方式

问题描述 投票:0回答:1

我见过很多使用

TaskEither
来表示发出 hhtp 请求或读取文件等的示例。
我想做的是模拟按 ID 在数据库中查找项目,因此操作的可能输出可能是:

  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
函数,以查看它确实正确处理不同的响应!好东西!

但是,亲爱的读者,我有一种感觉,有一种更聪明的方式来构建这篇文章。不幸的是我对 FP-TS 的了解仍然有限。
上面的代码你会怎么写?
谢谢x


编辑 我把范围缩小到这样的:

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))
    }
typescript functional-programming fp-ts
1个回答
0
投票

我认为您在编辑中最终得到的内容是尽可能干净的。您想要在所有可能的情况下做某事,因此

fold
-ing 或
match
-ing 是完成这项工作的工具。

如果您发现需要经常匹配这个精确的形状,并且您编写的函数体始终相同,您可以考虑编写一个助手,例如:

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);

这使定义变得扁平化,尽管在这种情况下编写明确的

Either
Option
匹配看起来并不太不清楚。

© www.soinside.com 2019 - 2024. All rights reserved.