【问题标题】:Throwing Exception inside of Future's flatMap?在 Future 的 flatMap 中抛出异常?
【发布时间】:2016-05-05 05:12:36
【问题描述】:

给定:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

scala>  Future(5).flatMap(_ => throw new Exception("!") )
res9: scala.concurrent.Future[Nothing] = Future(<not completed>)

scala> res9.value
res10: Option[scala.util.Try[Nothing]] = Some(Failure(java.lang.Exception: !))

为什么在Future#flatMap 中抛出异常会返回失败的未来?

鉴于其signature

def flatMap[S](f: (T) ⇒ Future[S])
              (implicit executor: ExecutionContext): Future[S]

_ =&gt; throw ... 如何返回Future

【问题讨论】:

    标签: scala


    【解决方案1】:

    嗯,首先flatMap 映射接收未来的结果,所以它立即返回另一个未来(因此不会立即捕获异常!)。传递给flatMap 的函数采用前一个未来的结果,并且必须产生另一个未来。抛出异常的类型为Nothing,它是任何其他类型的子类型,满足Nothing &lt;: Future[A],其中A 也被推断为Nothing

    从scala.concurrent的角度来看,flatMap(e =&gt; (throw new Exception()): Future[A])flatMap(e =&gt; Future[A](throw new Exception())必须同等对待,只有一个逻辑解决方案,返回一个失败的future。

    【讨论】:

    • 你能辩护吗,must be treated the same, there is only one logical solution, return a failed future.?为什么传播的异常不是逻辑结果?
    • x.flatMap[A](fun: B =&gt; Future[A]): Future[A] 无法在 x 完成之前应用 fun。因此它必须返回一个Future[A] 类型的值,也就是说,它只能通过创建一个内部Promise[A] 来实现。当x 准备好时,它必须完成那个未来,否则你会违反合同并使系统陷入困境。因此,它必须保护fun 本身的应用程序免受异常的影响。 正在传播异常
    • @KevinMeredith github.com/scala/scala/blob/… 注意 try/catch。
    【解决方案2】:

    throw 返回一个“底部类型”Nothinghttp://www.scala-lang.org/api/2.10.4/index.html#scala.Nothing,它是所有其他类型的子类型。因此,在 Future 中抛出异常会被捕获并返回 Future.failed。

    为什么在 Future#flatMap 中抛出异常会返回一个 失败的未来?

    因为它应该是这样工作的?你还能期待什么?

    【讨论】:

    • 我不清楚为什么异常被捕获,而不是在Future之外传播。
    • 答案在于“外面是什么?”提示:ExecutionContext
    【解决方案3】:

    我不明白@Falmarri 说什么,恕我直言,在 Future 中抛出异常会返回失败的 Future(顺便说一下,这是我期望的工作方式)仅仅是因为异常被捕获(老派 java 尝试抓住)

    考虑 Scala 2.12

    当您使用apply 方法(Future { }Future.apply 的语法糖)创建Future 时,您实际上是mapping a successful Future

    val unit: Future[Unit] = successful(())
    
    def apply[T](body: =>T)(implicit executor: ExecutionContext): Future[T] = unit.map(_ => body)
    

    map 是对 transform 的调用

    def map[S](f: T => S)(implicit executor: ExecutionContext): Future[S] = transform(_ map f)
    

    DefaultPromise 中的 transformimplementation 对非致命异常进行了尝试捕获

      override def transform[S](f: Try[T] => Try[S])(implicit executor: ExecutionContext): Future[S] = {
        val p = new DefaultPromise[S]()
        onComplete { result => p.complete(try f(result) catch { case NonFatal(t) => Failure(t) }) } // Here is the magic
        p.future
      }
    

    你可以试试在onComplete那一行加断点,然后再做

    Future { throw new Exception("!") } // or Future.apply(throw new Exception("!")) or Future.unit.map(_ => throw new Exception("!"))
    

    try f(result) 将调用 Success 的 map 函数,将其传递给您的函数(因为 Future.unit 的值是 Success(()) ),因为您的函数抛出异常,它将被 catch { case NonFatal(t) =&gt; Failure(t) } 捕获

    flatMap 会发生类似的情况,但在本例中是 calls transformWith

      def flatMap[S](f: T => Future[S])(implicit executor: ExecutionContext): Future[S] = transformWith {
        case Success(s) => f(s)
        case Failure(_) => this.asInstanceOf[Future[S]]
    }
    

    transformWithalso does 尝试捕获

      override def transformWith[S](f: Try[T] => Future[S])(implicit executor: ExecutionContext): Future[S] = {
        val p = new DefaultPromise[S]()
        onComplete {
          v => try f(v) match {
            case fut if fut eq this => p complete v.asInstanceOf[Try[S]]
            case dp: DefaultPromise[_] => dp.asInstanceOf[DefaultPromise[S]].linkRootOf(p)
            case fut => p completeWith fut
          } catch { case NonFatal(t) => p failure t } // Here is the magic
        }
        p.future
    }
    

    不过我不是 scala 专家,只是做了一些调试,所以不要把我说的当成理所当然。

    【讨论】:

    • 奖励:一个issue,关于何时未捕获异常并且 Future 永远不会完成。是边缘情况
    猜你喜欢
    • 2011-09-07
    • 1970-01-01
    • 1970-01-01
    • 2012-01-24
    • 1970-01-01
    • 2013-05-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多