【问题标题】:Scala: compose results of futures with exception handlingScala:使用异常处理组合期货结果
【发布时间】:2015-03-29 16:55:25
【问题描述】:

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

  1. 对另一个 API 进行 n 次调用,每次调用都有超时时间
  2. 合并所有结果(将列表列表转换为 包含所有元素)
  3. 如果单个调用失败,记录错误并返回一个空列表(基本上在这种情况下,如果我得到部分结果而不是完全没有结果会更好)
  4. 理想情况下,如果单个调用失败,在等待一段时间后重试 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 也是如此,但我想我在 SO 上的某个地方读到,在调用未来时尝试捕获不是一个好主意,最好使用恢复。但是,在这种情况下,如果我使用恢复,如果有单个失败,则整个调用都会失败并返回一个空列表。关于如何改进这一点的任何想法?

【问题讨论】:

    标签: scala concurrency


    【解决方案1】:

    实际上有两件事您可以做不同的事情:将 try-catch 排除在外,不要将 Await 与 Futures 一起使用。

    这是实现pollGuests的更好方法:

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

    这里的第一点是你不必在pollGuests() 中使用Futures,因为getGuests() 已经给了你一个Future。您只需使用recover() 创建一个新的 Future,以便在您返回 Future 时已经处理可能的失败

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

    【讨论】:

    • 我想我理解您所做的更改,但它给了我几个编译错误(类型不匹配)。
    • 哦,你是对的。现在它已经修复了。确实,您必须先创建列表列表的未来,然后才能将其展平。
    • 是的。现在可以了。我忘了提到的,这是等待的一部分是超时。在这种情况下,您将如何处理超时?
    • 超时其实并不是一件简单的事情。您可以使用 Await ,但这并不是一个很好的解决方案,因为阻塞。 This answer 为您提供了一个更好的解决方案,但是非常复杂。我会说你应该如何实现你的超时取决于用例。我会先说:你真的需要超时吗?
    • 好的,我去看看。谢谢!
    猜你喜欢
    • 2017-09-30
    • 1970-01-01
    • 2014-06-15
    • 2020-08-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多