【问题标题】:How to deal with exceptions in Scala Futures?如何处理 Scala Futures 中的异常?
【发布时间】:2011-08-08 11:14:04
【问题描述】:

我实现了一个简单的作业处理器,用于处理期货 (scala.actors.Futures) 中的子作业。这些期货本身可以为处理子作业创建更多期货。现在,如果其中一个子作业引发异常,我希望作业处理器回复该作业的错误消息。我有一个发现失败子作业的解决方案,但我不确定这是否是最好的解决方案。基本上它是这样工作的:

sealed trait JobResult
case class SuccessResult(content: String) extends JobResult
case class FailedResult(message: String) extends JobResult

for(subjob <- subjobs) yield {
  future {
    try {
          SuccessResult(process(subjob))
    } catch {
      case e:Exception => FailedResult(e.getMessage)                              
    }
  }
}

顶层的结果是 JobResults 的 Lists of Lists... 的递归 List。我递归地在列表中搜索失败的结果,然后根据结果的类型返回错误或组合结果。 这行得通,但我想知道是否有更优雅/更简单的解决方案来处理期货中的异常?

【问题讨论】:

    标签: scala future


    【解决方案1】:

    你现在做的方式,本质上就是 scala.Either 的设计目的。见http://www.scala-lang.org/api/current/scala/Either.html

    【讨论】:

    • 是的,使用无论是,还是更好的,来自 scalaz 的验证。
    • 谢谢,我会看看这两个建议:)
    【解决方案2】:

    现代 scala 期货 类似于 Either,因为它们包含成功的结果或 Throwable。如果您在 scala 2.10 中重新访问此代码,我想您会发现这种情况非常愉快。

    具体来说,scala.concurrent.Future[T] 在技术上只是“is-a”Awaitable[T],但_.onCompleteAwait.ready(_, timeout).value.get 都将其结果呈现为scala.util.Try[T],这很像Either[Throwable, T],因为它要么是结果或异常。

    奇怪的是,_.transform 有两个映射函数,一个用于T =&gt; U,一个用于Throwable =&gt; Throwable,并且(除非我遗漏了什么)没有将未来映射为Try[T] =&gt; Try[U] 的转换器。 Future.map 将允许您通过简单地在映射函数中抛出异常来将成功变为失败,但它仅将其用于原始Future 的成功。它的.recover,同样可以化失败为成功。如果您希望能够将成功变为失败,反之亦然,您需要自己构建一些 _.map_.recover 的组合,或者使用 _.onComplete 链接到新的 scala.concurrent.Promise[U]像这样:

    import scala.util.{Try, Success, Failure}
    import scala.concurrent.{Future, Promise}
    import scala.concurrent.ExecutionContext
    
    def flexibleTransform[T,U](fut: Future[T])(f: Try[T] => Try[U])(implicit ec: ExecutionContext): Future[U] = {
      val p = Promise[U]
      fut.onComplete { res =>
        val transformed = f(res)
        p.complete(transformed)
      }
      p.future
    }
    

    会这样使用:

    import scala.concurrent.ExecutionContext.Implicits.global
    import scala.concurrent.Await
    import scala.concurrent.duration.Duration.Inf
    
    def doIt() {
      val a: Future[Integer] = Future {
        val r = scala.util.Random.nextInt
        if (r % 2 == 0) {
          throw new Exception("we don't like even numbers")
        } else if (r % 3 == 0) {
          throw new Exception("we don't like multiples of three")
        } else {
          r
        }
      }
    
      val b: Future[String] = flexibleTransform(a) {
        case Success(i) =>
          if (i < 0) {
            // turn negative successes into failures
            Failure(new Exception("we don't like negative numbers"))
          } else {
            Success(i.toString)
          }
        case Failure(ex) =>
          if (ex.getMessage.contains("three")) {
            // nevermind about multiples of three being a problem; just make them all a word.
            Success("three")
          } else {
            Failure(ex)
          }
      }
    
      val msg = try {
        "success: " + Await.result(b, Inf)
      } catch {
        case t: Throwable =>
          "failure: " + t
      }
      println(msg)
    }
    
    for { _ <- 1 to 10 } doIt()
    

    这会给出这样的结果:

    failure: java.lang.Exception: we don't like even numbers
    failure: java.lang.Exception: we don't like negative numbers
    failure: java.lang.Exception: we don't like negative numbers
    success: three
    success: 1756800103
    failure: java.lang.Exception: we don't like even numbers
    success: 1869926843
    success: three
    failure: java.lang.Exception: we don't like even numbers
    success: three
    

    (或者你可以将Future“拉皮条”成一个带有隐式def的RichFutureWithFlexibleTransform,并使flexibleTransform成为它的成员函数,删除fut参数并简单地使用this

    (最好将Try[T] =&gt; Future[U] 命名为flexibleFlatMap,这样您就可以在转换中执行异步操作)

    【讨论】:

    • 你可能想提到类型:Try
    猜你喜欢
    • 1970-01-01
    • 2014-01-15
    • 1970-01-01
    • 1970-01-01
    • 2020-11-13
    • 2020-02-07
    • 2017-12-20
    • 2017-09-30
    • 1970-01-01
    相关资源
    最近更新 更多