Reputation: 684
I am new to Future in Scala and I haven't found the solution to my problem yet. I am trying to achieve the following (overall description: trying to get the list of guests for a list of hotels, querying each hotel separately):
Here is my code. HotelReservation
represents the external API I would call.
import com.typesafe.scalalogging.slf4j.Logging
import scala.concurrent._, ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
case class Guest(name: String, country: String)
trait HotelReservation extends Logging {
def getGuests(id: Int): Future[List[Guest]] = Future {
logger.debug(s"getting guests for id $id")
id match {
case 1 => List(new Guest("John", "canada"))
case 2 => List(new Guest("Harry", "uk"), new Guest("Peter", "canada"))
case 3 => {
Thread.sleep(4000)
List(new Guest("Harry", "austriala"))
}
case _ => throw new IllegalArgumentException("unknown hotel id")
}
}
}
object HotelReservationImpl extends HotelReservation
HotelSystem
makes the calls.
import com.typesafe.scalalogging.slf4j.Logging
import scala.util.control.NonFatal
import scala.util.{Failure, Success}
import scala.concurrent._, duration._, ExecutionContext.Implicits.global
class HotelSystem(hres: HotelReservation) {
def pollGuests(hotelIds: List[Int]): Future[List[Guest]] = {
Future.sequence(
hotelIds.map { id => future {
try {
Await.result(hres.getGuests(id), 3 seconds)
} catch {
case _: Exception =>
Console.println(s"failed for id $id")
List.empty[Guest]
}
}
}
).map(_.fold(List())(_ ++ _)) /*recover { case NonFatal(e) =>
Console.println(s"failed:", e)
List.empty[Guest]
}*/
}
}
And the test.
object HotelSystemTest extends App {
Console.println("*** hotel test start ***")
val hres = HotelReservationImpl
val hotel = new HotelSystem(hres)
val result = hotel.pollGuests(List(1, 2, 3, 6))
result onSuccess {
case r => Console.println(s"success: $r")
}
val timeout = 5000
Console.println(s"waiting for $timeout ms")
Thread.sleep(timeout)
Console.println("*** test end ***")
}
1 and 2 are working. So is 3 but I think I read somewhere on SO that having a try catch around the call to the future is not a good idea and it's better to use recover. However, in this case, if I use recover, if there is an individual failure, the whole call fails and return an empty list. Any ideas about how to improve this?
Upvotes: 2
Views: 701
Reputation: 606
There is actually two things that you could have done differently: leave the try-catch out and don't use Await with Futures.
Here is a better way to implement the pollGuests
:
Future.sequence(
hotelIds.map { hotelId =>
hres.getGuests(hotelId).recover {
case e: Exception => List.empty[Guest]
}
}
).map(_.flatten)
The first point here is that you don't have to use Futures inside the pollGuests()
since the getGuests()
already gives you a Future. You just create a new Future with the recover()
so that the possible failure is already handled when you return the Future
The second point is that you shouldn't use Await. It makes your code block until the Future is ready which could for example freeze your whole UI thread. I assume that you were using the Await to be able to use try-catch but thanks to the recover()
you don't need that anymore.
Upvotes: 5