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):
- Make n calls to another API, with a time out for each call
- Combine all the results (converting a list of lists into a list that contains all the elements)
- If an individual call fails, log the error and return an empty list (essentially in this case it's better if I get partial results rather than no results at all)
- Ideally, if an individual call fails, retry x times after waiting for a certain period of time and eventually fail and handle the error as if there was no retry
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?