А как насчет метода Scala Future#collectWith?
Там вы map
/flatMap
методы, есть также recover
/recoverWith
методы в стандартном API Scala Future.
Почему нет collectWith
?
Код collect
метод довольно прост:
def collect[S](pf: PartialFunction[T, S])(implicit executor: ExecutionContext): Future[S] =
map {
r => pf.applyOrElse(r, (t: T) => throw new NoSuchElementException("Future.collect partial function is not defined at: " + t))
}
Код collectWith
Метод тогда легко представить:
def collectWith[S](pf: PartialFunction[T, Future[S]])(implicit executor: ExecutionContext): Future[S] =
flatMap {
r => pf.applyOrElse(r, (t: T) => throw new NoSuchElementException("Future.collect partial function is not defined at: " + t))
}
Я знаю, что могу реализовать его и легко "расширить" стандарт API будущего благодаря этой статье: http://debasishg.blogspot.fr/2008/02/why-i-like-scalas-lexically-scoped-open.html
Я сделал это в моем проекте:
class RichFuture[T](future: Future[T]) {
def collectWith[S](pf: PartialFunction[T, Future[S]])(implicit executor: ExecutionContext): Future[S] =
future.flatMap {
r => pf.applyOrElse(r, (t: T) => throw new NoSuchElementException("Future.collect partial function is not defined at: " + t))
}
}
trait WithRichFuture {
implicit def enrichFuture[T](person: Future[T]): RichFuture[T] = new RichFuture(person)
}
Может быть, мои потребности для этого не оправдывают его реализацию в стандартном API?
Вот почему мне нужен этот метод в моем проекте Play2:
def createCar(key: String, eligibleCars: List[Car]): Future[Car] = {
def handleResponse: PartialFunction[WSResponse, Future[Car]] = {
case response: WSResponse if response.status == Status.CREATED => Future.successful(response.json.as[Car])
case response: WSResponse
if response.status == Status.BAD_REQUEST && response.json.as[Error].error == "not_the_good_one" =>
createCar(key, eligibleCars.tail)
}
// The "carApiClient.createCar" method just returns the result of the WS API call.
carApiClient.createCar(key, eligibleCars.head).collectWith(handleResponse)
}
Я не знаю, как это сделать без моего collectWith
метод.
Может быть, это не правильный способ сделать что-то подобное?
Ты знаешь лучший способ?
РЕДАКТИРОВАТЬ:
У меня может быть лучшее решение для createCar
метод, который не требует collectWith
метод:
def createCar(key: String, eligibleCars: List[Car]): Future[Car] = {
for {
mayCar: Option[Car] <- Future.successful(eligibleCars.headOption)
r: WSResponse <- carApiClient.createCar(key, mayCar.get) if mayCar.nonEmpty
createdCar: Car <- Future.successful(r.json.as[Car]) if r.status == Status.CREATED
createdCar: Car <- createCar(key, eligibleCars.tail) if r.status == Status.BAD_REQUEST && r.json.as[Error].error == "not_the_good_one"
} yield createdCar
}
Что вы думаете об этом втором решении?
Второе редактирование:
Просто для информации, вот мое окончательное решение благодаря ответу @Dylan:
def createCar(key: String, eligibleCars: List[Car]): Future[Car] = {
def doCall(head: Car, tail: List[Car]): Future[Car] = {
carApiClient
.createCar(key, head)
.flatMap( response =>
response.status match {
case Status.CREATED => Future.successful(response.json.as[Car])
case Status.BAD_REQUEST if response.json.as[Error].error == "not_the_good_one" =>
createCar(key, tail)
}
)
}
eligibleCars match {
case head :: tail => doCall(head, tail)
case Nil => Future.failed(new RuntimeException)
}
}
Жюль
1 ответ
Как насчет:
def createCar(key: String, eligibleCars: List[Car]): Future[Car] = {
def handleResponse(response: WSResponse): Future[Car] = response.status match {
case Status.Created =>
Future.successful(response.json.as[Car])
case Status.BAD_REQUEST if response.json.as[Error].error == "not_the_good_one" =>
createCar(key, eligibleCars.tail)
case _ =>
// just fallback to a failed future rather than having a `collectWith`
Future.failed(new NoSuchElementException("your error here"))
}
// using flatMap since `handleResponse` is now a regular function
carApiClient.createCar(key, eligibleCars.head).flatMap(handleResponse)
}
Два изменения:
handleResponse
больше не является частичной функцией.case _
возвращает неудачное будущее, что, по сути, вы делали в своем обычаеcollectWith
реализация.- использование
flatMap
вместоcollectWith
, посколькуhandleResponse
теперь подходит для этого метода подписи
редактировать для дополнительной информации
Если вам действительно нужно поддержать PartialFunction
подход, вы всегда можете преобразовать PartialFunction[A, Future[B]]
к Function[A, Future[B]]
позвонив orElse
на частичной функции, например
val handleResponsePF: PartialFunction[WSResponse, Future[Car]] = {
case ....
}
val handleResponse: Function[WSResponse, Future[Car]] = handleResponsePF orElse {
case _ => Future.failed(new NoSucheElementException("default case"))
}
Это позволит вам адаптировать существующую частичную функцию к flatMap
вызов.
(хорошо, технически, это уже происходит, но вы будете генерировать MatchErrors, а не свои собственные исключения)