Reputation: 6005
Let say that in my Controller I have a set of Sms objects. For each one of those objects I would like to call a web service and then for all the successful calls return the sms objects to view.
Controller:
def inviteBySms() = Action {
implicit request => {
val smsSet = getSet()
Async {
smsSet.map(sms => callSmsService(sms).map(response => {
response.status match {
case 200 => {
// somehow add the sms object to a success set
}
case _ => {
// ignore
}
}
}))
// Return Ok() with the success Set[SMS]
}
}
}
method in Controller:
def postToService(sms: Sms) = {
val params = Map(Seq(current.configuration.getString("sms.service.user").getOrElse("")),
"pass" -> Seq(current.configuration.getString("sms.service.password").getOrElse("")),
"mobilephone" -> Seq(sms.number))
)
val futureResponse = WS.url(Play.current.configuration
.getString("sms.service.url").getOrElse(""))
.withHeaders("Content-Type" -> "application/x-www-form-urlencoded; charset=utf-8")
.post(params)
}
Upvotes: 1
Views: 188
Reputation: 4181
After further discussion, here is a most correct answer than my first attempt.
In my code I've just defined Sms as type Sms = String
and I'm just calling directly WS.url().get on it.
First, the callSmsService(sms: Sms)
should call the service and not return the Response
but already map()
it depending on whether the return is 200 or not. The idea is to return back the original Sms
instance or None
so that we can aggregate them later :
def callSmsService(sms: Sms): Future[Option[Sms]] = {
WS.url(sms).get
.map {
response => if (response.status == 200) Some(sms) else None
}
}
You can now map()
your initial Sms set with this function:
val futureOptionSet: Set[Future[Option[Sms]]] = smsSet.map { sms => callSmsService(sms) }
Then, you have to transform the Set[Future]
into a global Future[Set]
(you need a unique Future to construct a Future[Result]
asked by Async{...}
):
val optionSetFuture: Future[Set[Option[Sms]]] = Future.sequence(futureOptionSet)
Now, you can map the global Future to collect only successfull Sms
val smsSetFuture: Future[Set[Sms]] = optionSetFuture.map { set => set.collect { case Some(sms) => sms }}
With this last Future
, you can now do a map()
to build a Future[Response]
smsSetFuture.map( successfulSmsSet: Set[Sms] => {
// do something with successfulSmsSet
ok(...)
})
I have detailed step by step with explicit return types but you can of course combine several steps without the intermediate values and explicit types.
Upvotes: 1
Reputation: 4181
First, I would call :
smsSet.map(sms => callSmsService(sms))
to get a Set[Future[Response]] and wrap it with Future.sequence to get a Future[Set[Response]] :
Future.sequence(smsSet.map(sms => callSmsService(sms)))
And then on the Future[Set[Response]] you can filter() and map() to get a Future[Set[SMS]]
val futureSetSms = Future.sequence(smsSet.map(sms => callSmsService(sms)))
.filter{_.status == 200}
.map(response => {...construct your SMS...})
You then just have to map that to a Future[Result] that can be used by Async{} :
futureSetSms.map { smsSet => Ok(...) }
Upvotes: 0