Await.result on HttpService

209 views Asked by At

I've a scala project with http4s 0.15.16a and slick 3.2.1 with these steps:

  1. Receive a ID by rest call
  2. passing ID to MySlickDAO that responds with a Future
  3. Call Await.result(res, Duration.Inf) on Future returned by MySlickDAO
  4. Create the json

The problem is that I use a Await.result and this is bad practices is there a better solution ?

Here the code:

  val service = HttpService {

//http://localhost:8080/rest/id/9008E75A-F112-396B-E050-A8C08D26075F
case GET -> Root / "rest" / "id" / id =>

  val res = MySlickDAO.load(id)

  Await.result(res, Duration.Inf)

  val ll = res.value.get.get
  ll match {
    case Failure(x) =>
      InternalServerError(x)
    case Success(record) =>
      val r = record.map(x => MyEntity(x._1, x._2, x._3))
      jsonOK(r.asJson)
  }
 case ....

}

2

There are 2 answers

1
Levi Ramsey On

Instead of awaiting, you can chain the result of one Future into another:

val resFut = MySlickDAO.load(id)
resFut.map { record =>
   val r = record.map(x => MyEntity(x._1, x._2, x._3))
   jsonOK(r.asJson)
} recover { x =>
   InternalServerError(x)
}

The result of this will be a Future of a common supertype of jsonOK and InternalServerError (not familiar with the libraries you're using; so I may have the type of load wrong: it's not a Future[Try[_]] is it?).

BTW: your original code has a very problematic line:

val ll = res.value.get.get

res.value is an Option[Try[T]]. Calling get on an Option or a Try is generally a bad idea (even though in this case because of the Await, the Option should never be None, so the get is technically safe) because it can throw an exception. You're much better off using map, flatMap, and friends.

0
Astrid On

The issue is that http4s 0.15 uses the Scalaz concurrency constructs, while Slick uses the native Scala ones, and the two aren't designed to work with each other. My understanding is that http4s 0.17+ has switched from Scalaz to Cats, which might entail using native Scala Futures, so if you can upgrade that might be worth a shot. If not, you can handle the conversion by manually creating a Task that wraps your future:

def scalaFutureRes = MySlickDAO.load(id)
val scalazTaskRes = Task.async { register =>
  scalaFutureRes.onComplete {
    case Success(success) => register(success.right)
    case Failure(ex)      => register(ex.left)
  }
}

At this point you've got a Task[ResultType] from the Future[ResultType] which you can map/flatMap with the rest of your logic like in Levi's answer.

You can also use the delorean library for this which has this logic and the opposite direction defined on the classes in question via implicit conversions, so that you can just call .toTask on a Future to get it in a compatible form. Their readme also has a lot of useful information on the conversion and what pitfalls there are.