TypeScript/fp-ts 中 Either 的类型错误

Type error with Either in TypeScript/fp-ts

我正在使用 fp-ts,我有一个函数 returns 一个 HttpError 对象或一个字符串:

async getPreferencesForUserId(userId: string): Promise<Either<HttpResponseNotFound, string>> {
    const preferences = await getRepository(Preference).findOne({ userId });
    return preferences ? right(preferences.preferenceMap) : left(new HttpResponseNotFound({ code: 404, message: 'Could not find preferences' }));
  }

我想像这样在另一个文件中调用这个函数:

const preferenceMapAsJsonStringOrError: Either<HttpResponseNotFound, string> = await this.preferenceService.getPreferencesForUserId(userId);

const response: HttpResponseOK | HttpResponseNotFound = pipe(preferenceMapAsJsonStringOrError, fold(
  e => e,
  r => new HttpResponseOK(r)
));
response.setHeader('content-type', 'application/json');

return response;

这基本上就是我在 Scala 中的做法。 (除了 fold 是 Either 类型上的一个方法,而不是一个独立的函数——所以我在这里使用 pipe 助手)

问题是,我收到来自 ts-server 的错误:

Type 'HttpResponseOK' is missing the following properties from type 'HttpResponseNotFound': isHttpResponseNotFound, isHttpResponseClientError

node_modules/fp-ts/lib/Either.d.ts:129:69                                                                           
    129 export declare function fold<E, A, B>(onLeft: (e: E) => B, onRight: (a: A) => B): (ma: Either<E, A>) => B;
                                                                            ~~~~~~~~~~~
    The expected type comes from the return type of this signature.

我可以通过更命令的方式解决这个问题:

const preferenceMapAsJsonStringOrError: Either<HttpResponseNotFound, string> = await this.preferenceService.getPreferencesForUserId(userId);
if (isLeft(preferenceMapAsJsonStringOrError)) {
  return preferenceMapAsJsonStringOrError.left;
}

const response = new HttpResponseOK(preferenceMapAsJsonStringOrError.right);
response.setHeader('content-type', 'application/json');

return response;

但那时我几乎失去了使用 Either 的好处。

问题是,考虑到 TS 推理的工作原理,当使用 fold 时,其 return 类型是 "fixed" 到第一个参数之一 (onLeft ), onRight 无法 "widen" 它 HttpResponseNotFound | HttpResponseOK.

换句话说,一般情况下使用TS和fp-ts是不会免费统一的

对于这种特定情况,我建议

  1. 为输出中想要的联合类型命名(并非绝对必要,但有助于阐明意图):
type HttpResponse = HttpResponseNotFound | HttpResponseOK
  1. 明确地 "widen" return 类型的折叠。这必须手动完成,或者通过注释 onLeft fold 参数的 return 类型:
const response: HttpResponse = pipe(
  preferenceMapAsJsonStringOrError,
  E.fold((e): HttpResponse => e, r => new HttpResponseOK(r))
)

或者定义一个 widen 助手如下:

const widen = E.mapLeft<HttpResponse, HttpResponse>(e => e);

const response: HttpResponse = pipe(
  preferenceMapAsJsonStringOrError,
  widen,
  E.fold(identity, r => new HttpResponseOK(r))
);

希望这对您有所帮助:)

尝试这两种方法后,我仍然遇到类型错误。为我修复它的是明确指定折叠的类型。

fold<HttpResponseNotFound, HttpResponseOK, HttpResponseNotFound | HttpResponseOK>(
  e => e,
  r => new HttpResponseOK(r)
)