Reputation: 1536
I have some simple enum name Result of fail/success
sealed trait Result
case object Success extends Result
case object Fail extends Result
and I have a function that get nothing and returns Future of Result,
def do(): Future[Result] = {
someService.getInfo() flatMap { responseInfo =>
responseInfo.fold(Future.successful[Result](Result)) { info =>
myDB.getData(info.resId) map { dbRes =>
if (dbRes.isSomething) Fail else Success
} recover {
case e: NotFoundException => Fail
}
}
}
}
I dont like my solution, it looks to messy, is there a simpler one?
someService.getInfo
returns Future[Option[ResponseInfo]]
and what i want to do here is incase someService.getInfo
returns None so the func should return Future[Fail] else to call the db, which also returns Future of a case class and base on the response to return Fail/Success.
but im looking for a super stylish scala way to make it look good :)
thanks!
Upvotes: 1
Views: 2767
Reputation: 28511
You're creating a new concept of Success
and Failure
, which a scala.concurrent.Future
already has, so what you are doing is pretty pointless.
You have all the failure state representation you could want, because inside a Future
hides a scala.util.Try
, so you can already match
, fold
, etc, any other normal flow control method is there.
def do(): Future[Option[DbResult]] = {
someService.getInfo() flatMap { responseInfo =>
responseInfo.fold(Future.successful(_)) { info =>
myDB.getData(info.resId) map { dbRes =>
if (dbRes.isSomething) Some(dbRes) else None
} recover {
case e: NotFoundException => Future.failed(e)
}
}
}
}
It's also not a good idea to conflate notions. E.g "not finding" a result in the database for a given query is a very valid state, and it has nothing to do with your failed future.
Failures in a future should represent network failures, malformed queries, etc, not a valid roundtrip where the database correctly returned no results. For that reason you can use Option
.
Monad transformers
This is a generic problem, of transforming G[F[_]]
to F[G[_]]
, where you have interesting combinations of wrapped types to deal with. Say Future[Option[T]]
to Option[Future[T]]
. The functional way of dealing with this stuff is a little bit hairy, but worth it.
import cats.data.OptionT
import cats.std.future._
def do() = {
val chain = for {
info <- OptionT(someService.getInfo())
data <- OptionT(myDB.getData(info.resId) map { dbRes =>
if (dbRes.isSomething) Some(dbRes) else None
} recover {
case e: NotFoundException => Future.failed(e)
})
} yield data
chain.value
}
It would look similar to above. Read more about OptionT
here, but it looks like what you want.
Upvotes: 2