Scala Cats EitherT异常捕获差异及循环优雅容错方案问询
问题1解答
原因在于异常抛出的时机和线程上下文完全不同:
- 当
calculateOne抛出异常时,这个异常是在主线程同步执行阶段抛出的——调用calculateOne()是直接在try代码块里同步执行的,还没进入Future的异步逻辑,所以外层的try-catch能直接捕获到这个异常。 - 而
calculateTwo的调用是在EitherT的flatMap逻辑中(也就是for推导的后续步骤),这部分代码属于异步执行逻辑:当calculateTwo抛出异常时,异常会被封装到Future.failed实例中,但外层的try-catch只负责同步执行创建EitherT的代码,并不会等待异步Future完成,且异步线程的异常不会冒泡到主线程的try-catch调用栈里,因此无法被捕获。
注:你的calculateTwo方法目前缺少返回值,实际运行会编译报错,需补全返回逻辑(比如Future.failed(new RuntimeException("error from two")))。
问题2解答
要统一处理同步/异步异常且不混用try-catch,可以借助Cats的EitherT和ScalaFuture的错误处理工具,把所有异常都纳入Either的左值体系,实现统一的错误处理:
优化后的代码示例
package com.oxo.test import cats.data.EitherT import cats.implicits._ import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import scala.util.Try object FutureError extends App { val processingList = List(1, 2, 3) processingList.foreach { x => // 用EitherT封装整个流程,统一处理所有异常 val processingFlow = for { result1 <- EitherT(calculateOne(x)) result2 <- EitherT(calculateTwo(x)) result3 <- EitherT(calculateThree(x)) sum = result1 + result2 + result3 } yield sum // 统一捕获所有异常(同步抛出+Future失败),转换成Either左值 val handledFlow = processingFlow.recover { case e: Exception => s"Processing failed: ${e.getMessage}" } // 处理最终的Either结果 handledFlow.value.map { case Right(sum) => println(s"Success processing $x: sum = $sum") case Left(errMsg) => println(s"Error processing $x: $errMsg") } } // 封装同步异常到Future[Either],避免直接抛出 private def calculateOne(input: Int): Future[Either[String, Int]] = { Future.fromTry(Try(throw new RuntimeException("error from one"))) .map(_.left.map(_.getMessage)) } private def calculateTwo(input: Int): Future[Either[String, Int]] = { Future.successful(Right(input + 2)) } private def calculateThree(input: Int): Future[Either[String, Int]] = { Future.successful(Right(input + 3)) } }
核心思路
- 封装同步异常:用
Future.fromTry(Try(...))把可能抛出同步异常的逻辑包裹起来,确保同步异常被转换成Future[Either]的左值,而不是直接在主线程抛出。 - 统一错误处理:利用
EitherT.recover方法捕获所有异常(包括异步Future的失败和同步抛出的异常),将其转换成统一的错误信息字符串,纳入Either的左值体系。 - 单一结果处理入口:最后只需要处理
Either的Right(成功)和Left(错误)两种情况即可,无需单独使用try-catch或Future.recover。
内容的提问来源于stack exchange,提问作者Yalav
相关产品推荐
相关产品推荐

