Scala:用异常处理编写期货结果

时间:2015-03-29 16:55:26

标签: scala concurrency

我是Scala的Future新手,但我还没有找到解决问题的方法。我正在努力实现以下目标(整体描述:尝试获取酒店列表的客人名单,分别查询每家酒店):

  1. 调用另一个API,每次调用超时
  2. 合并所有结果(将列表列表转换为列表 包含所有元素)
  3. 如果单个呼叫失败,请记录错误并返回一个空列表(基本上在这种情况下,如果我得到部分结果而不是根本没有结果,那就更好了)
  4. 理想情况下,如果单个呼叫失败,请在等待一段时间后重试x次并最终失败并处理错误,就像没有重试一样
  5. 这是我的代码。 HotelReservation代表我要调用的外部API。

    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拨打电话。

    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]
    }*/
      }
    }
    

    测试。

    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和2正在运作。所以是3但我认为我在某个地方看到了试图捕捉未来的呼叫并不是一个好主意,最好使用恢复。但是,在这种情况下,如果我使用recover,如果有个别故障,整个调用将失败并返回一个空列表。关于如何改进这个的任何想法?

1 个答案:

答案 0 :(得分:5)

实际上有两件事你可以做的不同:把try-catch留下来,不要使用Await with Futures。

以下是实施pollGuests的更好方法:

Future.sequence(
   hotelIds.map { hotelId =>
      hres.getGuests(hotelId).recover {
         case e: Exception => List.empty[Guest]
      }
   }
).map(_.flatten)

这里的第一点是您不必在pollGuests()内使用期货,因为getGuests()已经为您提供了未来。您只需使用recover()创建一个新的Future,以便在返回Future时已经处理了可能的失败

第二点是你不应该使用Await。它会使你的代码阻塞,直到Future准备就绪,这可能会冻结你的整个UI线程。我假设您使用Await可以使用try-catch,但感谢recover()您不再需要它。