#typescript #fp-ts
Вопрос:
Я изучаю fp-ts и задаюсь вопросом, как я могу лучше организовать свои функции, чтобы избежать вложенных складок. Все примеры, которые я вижу в Интернете, имеют приятный оптимизированный вызов функции канала, но я не могу понять, как избежать вложенных складок.
Некоторый контекст — На высоком уровне цель этого кода состоит в том, чтобы создать местоположение и, если это удастся, создать Станцию. Если какая-либо операция завершится неудачно, верните вызывающему абоненту соответствующую ошибку. Если все в порядке, верните 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);
}
Ответ №1:
Представьте , что мы работаем с 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
тип для модели a 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 с заглушками.
Комментарии:
1. Спасибо, это очень полезно. Я не знал об
TE.throwError
этом ; это полезная конструкция. Одна вещь, которую я все еще пытаюсь понять, это почему нам нужно преобразовать первую часть в функцию, которая возвращает обещание. Т. Е. почему мы не можем просто использовать вызов службы вместоcreateLocationTask
? В связи с этим я замечаю, что в цепочке задач нетawait
s… Это для того, чтобы мы могли выполнять обещания на протяжении всего процесса — иawait
chainedTask
в конце концов?2. Первая часть, потому что это контракт, который вам нужно подписать перед использованием
pipe
функции. Труба требует, чтобы все в трубопроводе было одного типа. Поскольку нисходящий обработчикReturnType<typeof TE.fold>
указывает , что он принимает аргумент типаTaskEither
, который равен() => Promise<Either>
, нам нужно преобразовать вызов службы в этот тип, таким образомcreateLocationTask
.3. Вторая часть, я думаю, вы более или менее уловили суть. Просто для ясности, вставка нескольких ожиданий не причиняет вреда, но и не помогает. Ожидание полезно только тогда, когда вы хотите «развернуть» обещание, но у нас здесь нет прецедента.