fp-ts 如何处理管道内的异步操作

fp-ts How to Handle Async operations within pipe

我正在学习 fp-ts 并且想知道如何更好地组织我的函数以避免嵌套折叠。我在网上看到的所有示例都有一个很好的流线型 pipe 函数调用,但我不知道如何避免嵌套折叠。

一些上下文 - 在较高级别,此代码的目的是创建一个 Location,如果成功,则创建一个 Station.如果任何一个操作失败,return 返回一个适当的错误给调用者。如果一切顺利,return一个201。

public async initialize(
    @requestParam('site') site: string,
    @request() req: Request,
    @response() res: Response
  ) {
    //use the same value for now
    const nameAndPublicId = LocationService.retailOnlineLocationName(site);
    
    const location: E.Either<ApiError, LocationDTO> = await this.locationService.createLocation(
      site,
      nameAndPublicId,
      nameAndPublicId
    );

    const stationName: string = StationService.retailOnlineStationName(site);

    pipe(
      location,
      E.fold(
        (err: ApiError) => ConfigController.respondWithError(err, res),
        async (loc: LocationDTO) => {
          pipe(
            await this.stationService.createStation(site, stationName, loc.id),
            E.fold(
              (err: ApiError) => ConfigController.respondWithError(err, res),
              (_: StationDTO) => res.status(201).send()
            )
          );
        }
      )
    );
  }

  static respondWithError(err: ApiError, res: Response) {
    res.status(err.statusCode).json(err);
  }

想象一下我们正在使用 Promise,代码会是什么样子?您将使用 .then 链接所有良好案例处理代码,并仅附加一个带有最终 .catch.

的不良案例处理程序
public async initialize(
  @requestParam('site') site: string,
  @request() req: Request,
  @response() res: Response
) {
  const stationName: string = StationService.retailOnlineStationName(site);

  const nameAndPublicId = LocationService.retailOnlineLocationName(site);
  
  // for illustration purpose, we suppose here
  // the service returns a Promise of actual value
  // instead of Promise of Either
  await this.locationService.createLocation(
    site,
    nameAndPublicId,
    nameAndPublicId
  ).then((loc: LocationDTO) => {
    return this.stationService.createStation(site, stationName, loc.id)
  }).then((_: StationDTO) => {
    res.status(201).send()
  }).catch(err => {
    ConfigController.respondWithError(err, res),
  })
}

fp 版本应该具有相同的结构,只是类型不同。我们可以使用 TaskEither 类型来建模 Promise.

public async initialize(
  @requestParam('site') site: string,
  @request() req: Request,
  @response() res: Response
) {
  const stationName: string = StationService.retailOnlineStationName(site);

  const nameAndPublicId = LocationService.retailOnlineLocationName(site);
  
  // here the service shall return Promise of Either
  const createLocationTask = () => this.locationService.createLocation(
    site,
    nameAndPublicId,
    nameAndPublicId
  )

  const chainedTask = pipe(
    createLocationTask,
    TE.fold(
      TE.throwError, // pass through error
      (loc: LocationDTO) => async () => stationService.createStation(site, stationName, loc.id),
    ),
    TE.fold(
      // catch error
      (err: ApiError) => async () => ConfigController.respondWithError(err, res),
      (_: StationDTO) => async () => { res.status(201).send() },
    )
  )

  await chainedTask()
}

附件是带有存根的 ts playground 演示。

TS Playground