假设我有三个实体,一个父实体,代表通过第三方 API 摄取数组数组,一个子实体代表一个此类数组成员的解析和处理,错误代表处理失败以及错误消息。
假设我有以下类型:
export type CreateChildEntity = () => TaskEither<ServiceError, void>;
export type CheckParentMappable = (
id: UUID
) => TaskEither<ServiceError, Option<UUID>>;
export type GetHydratedParentForMapping = (
id: UUID
) => TaskEither<ServiceError, HydratedType>;
export type MarkParentMapped = (id: UUID) => TaskEither<ServiceError, void>;
export type MarkParentFailed = (id: UUID) => TaskEither<ServiceError, void>;
export type LookupDetails = (
id: UUID,
firstName: string,
lastName: string
) => TaskEither<ServiceError, Option<ValidatedDetails>>;
export type TrackParentMappingError = (
id: UUID,
author: ValidatedAuthor,
message: string
) => TaskEither<ServiceError, void>;
export type Deps = Readonly<{
createChildEntity: CreateChildEntity;
checkParentMappable: CheckParentMappable;
getHydratedParentForMapping: GetHydratedParentForMapping;
markParentMapped: MarkParentMapped;
markParentFailed: MarkParentFailed;
lookupDetails: LookupDetails;
trackParentMappingError: TrackParentMappingError;
}>;
然后是以下功能:
import { Ctx, Deps, Input, Error } from "./types";
import { pipe } from "fp-ts/lib/pipeable";
import * as TE from "fp-ts/lib/TaskEither";
import { isSome } from "fp-ts/lib/Option";
export default (input: Input): Ctx<void> => mapParent(input);
const mapParent = (input: Input): Ctx<void> => (
deps: Deps
): TE.TaskEither<Error, void> =>
pipe(
deps.getHydratedParentForMapping(input.id),
TE.chain(hydratedParent =>
pipe(
TE.sequenceArray(
hydratedParent.authors.map(a => {
return pipe(
deps.lookupDetails(hydratedParent.id, a.firstName, a.lastName),
TE.chain(detail => {
if (isSome(detail)) {
return deps.createChildEntity();
} else {
return pipe(
deps.trackParentMappingError(
hydratedParent.id,
a,
"MISSING_DETAILS_ERROR"
),
TE.chain(_ => deps.markParentFailed(hydratedParent.id))
);
}
})
);
})
),
TE.chain(_ => deps.markParentMapped(hydratedParent.id))
)
)
);
完整代码在这里:https ://stackblitz.com/edit/typescript-3qv4t1?file=index-bad.ts
只要我在里面有验证规则markParentFailed
并markParentMapped
确定父级确实(或没有)附加了子级/错误,这段代码就可以工作。
相反,我想要实现的是让 cross 的初始映射lookupDetails
返回hydratedParent.authors
一个 Either,这样我就可以根据故障的存在完全划分流。实际上,像这样:
const constructEither = (hydratedParent: HydratedType): Ctx<void> => (deps: Deps): TE.TaskEither<Error, Either<ReadonlyArray<Readonly<{id: UUID, author: ValidatedAuthor, errorMessage: string}>>, ReadonlyArray<ValidatedDetails>>> => {
return TE.sequenceArray(
hydratedParent.authors.map(a => {
return pipe(
deps.lookupDetails(hydratedParent.id, a.firstName, a.lastName),
TE.chain(val => {
if(isSome(val)) {
return right(val.value)
} else {
return left({id: hydratedParent.id, author: a, errorMessage: "MISSING_DETAILS_ERROR"})
}
})
)
}))
}
const mapParent = (input: Input): Ctx<void> => (
deps: Deps
): TE.TaskEither<Error, void> =>
pipe(
deps.getHydratedParentForMapping(input.id),
TE.chain(hydratedParent =>
pipe(
constructEither(deps)(hydratedParent),
TE.chain(res => {
if(isRight(res)) {
pipe(
res.map(r => deps.createChildEntity()),
,TE.chain(_ => deps.markParentMapped(hydratedParent.id))
)
} else {
pipe(
res.map(r => deps.trackParentMappingError(
r.left.id,
r.left.author,
r.left.errorMessage
)),
TE.chain(_ => deps.markParentFailed(hydratedParent.id))
)
}
})
)
)
);
这无法编译。
此外,我的理解是,即使我让它编译,TaskEither 无论如何都会忽略左分支,并且实际上将它作为异常抛出。
以这种方式对 TaskEither 进行分区的 fp-ts 咒语是什么?