Reputation: 3951
I'm writing an authentication client that takes an Option[Credentials]
as a parameter. This Credentials
object has a .token
method on it which I will then use to construct an HTTP request to post to an endpoint. This returns a Future[HttpResponse]
, which I then need to validate, unmarshal, and then convert back to my return type, which is an Option[String]
.
My first thought was to use a for comprehension like this:
val resp = for {
c <- creds
req <- buildRequest(c.token)
resp <- Http().singleRequest(req)
} yield resp
but then I found out that monads cannot be composed like that. My next thought is to do something like this:
val respFut = Http().singleRequest(buildRequest(token))
respFut.onComplete {
case Success(resp) => Some("john.doe")//do stuff
case Failure(_) => None
}
Unfortunately onComplete
returns a unit, and map
leaves me with a Future[Option[String]]
, and the only way I currently know to strip off the future wrapper is using the pipeTo
methods in the akka framework. How can I convert this back to just an option string?
Upvotes: 0
Views: 107
Reputation: 2690
I'm not 100% certain about what all your types are, but it seems like you want a for comprehension that mixes option and futures. I've often been in that situation and I find I can just chain my for comprehensions as a way to make the code look a bit better.
val resp = for {
c <- creds
req <- buildRequest(c.token)
} yield for {
resp <- Http().singleRequest(req)
} yield resp
resp becomes an Option[Future[HttpResponse]] which you can match / partial func around with None meaning the code never got to execute because it failed its conditions. This is a dumb little trick I use to make comprehensions look better and I hope it gives you a hint towards your solution.
Upvotes: 1
Reputation: 6182
It's easy to "upgrade" an Option
to a Future[Option[...]]
, so use Future
as your main monad. And deal with the simpler case first:
val f: Future[Option[String]] =
// no credential? just wrap a `None` in a successful future
credsOpt.fold(Future.successful(Option.empty[String])) {creds =>
Http()
.singleRequest(buildRequest(creds.token))
.map(convertResponseToString)
.recover {case _ => Option.empty[String]}
}
The only way to turn that future into Option[String]
is to wait for it with Await.result(...)
... but it's better if that future can be passed along to the next caller (no blocking).
Upvotes: 1
Reputation: 18434
Once you've got a Future[T]
, it's usually good practice to not try to unbox it until you absolutely have to. Can you change your method to return a Future[Option[String]]
? How far up the call stack can you deal with futures? Ideally it's all the way.
Something like this will give you a Future[Option[String]]
as a result:
val futureResult = creds map {
case Some(c) => {
val req = buildRequest(c.token)
val futureResponse = Http().singleRequest(req)
futureResponse.map(res => Some(convertResponseToString(res)))
}
case None => Future(None)
}
If you really need to block and wait on the result, you can do Await.result
as described here.
And if you want to do it in a more monadic style (in a for-comprehension, like you tried), cats has an OptionT type that will help with that, and I think scalaz does as well. But whether you want to get into either of those libraries is up to you.
Upvotes: 1