首页 文章

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

提问于
浏览
1

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

  • 对另一个API进行n次调用,每次调用超时

  • 合并所有结果(将列表列表转换为包含所有元素的列表)

  • 如果单个调用失败,请记录错误并返回一个空列表(基本上在这种情况下,如果我得到部分结果而不是根本没有结果,那就更好了)

  • 理想情况下,如果单个调用失败,请在等待一段时间后重试x次并最终失败并处理错误,就好像没有重试一样

这是我的代码 . 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 回答

  • 4

    实际上有两件事你可以做的不同:把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() 您不再需要它了 .

相关问题