我见过很多使用
TaskEither
来表示发出 hhtp 请求或读取文件等的示例。类似的接口是
TaskEither<Error, Option<A>>
:
type TaskEO<A> = TaskEither<Error, Option<A>>
由于我将发送结果作为 HTTP 响应(对
GET
查询的响应),因此我希望能够清楚地区分上述 3 个场景。每个的响应代码为:
我编写了以下代码来将 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))
}
我认为您在编辑中最终得到的内容是尽可能干净的。您想要在所有可能的情况下做某事,因此
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
匹配看起来并不太不清楚。