Scala中Await.result异常处理问题:自定义异常未正确捕获
我来帮你排查这个问题——你遇到的核心问题是None.get抛出的异常并不是来自你在recoverWith里捕获的Repository调用失败,而是来自你在for推导式后续步骤中直接调用了.get方法。这个异常发生在Future成功返回None之后,完全绕开了你之前定义的recoverWith逻辑,所以外层catch只能抓到通用的Exception,而不是你期望的自定义异常。
问题根源拆解
咱们一步步看代码逻辑:
- 当
rackRepository.getById(rack.id)成功返回None时,这个Future处于成功状态,所以你后面的recoverWith不会触发——recoverWith只处理Future的失败情况。 - 接下来你执行
futureSeqGpuRow <- gpuRepository.getByRack(futureRackRow.get.id),这里直接调用futureRackRow.get,因为futureRackRow是None,立刻抛出NoSuchElementException(也就是你看到的None.get异常)。 - 这个异常会被Scala自动包装进当前Future的失败通道,但它已经不是你定义的
RackException了,所以外层catch的自定义异常分支不会命中,只能走到通用的Exception分支。
解决方案:提前处理Option,避免.get
要解决这个问题,你需要在Future的处理流程中就把None的情况转换成自定义异常,而不是留到后面用.get硬取。具体可以这么做:
1. 把Option转换成Future的失败状态
在处理rackRepository.getById的结果时,用flatMap把Option[RackRow]转换成Future[RackRow]——如果是Some就返回成功的Future,如果是None就返回失败的Future(抛自定义异常)。这样不管是Repository调用失败,还是返回None,都会进入Future的失败通道,被你后续的recoverWith或者外层catch正确捕获。
2. 移除所有.get调用
既然已经把Option转换成了确定的RackRow,后续代码就不需要再用.get了,直接使用对象的属性即可。
修改后的完整代码
try { val futureResult = for { // 先处理Option:None直接转换成RackException的失败Future rackRow <- rackRepository.getById(rack.id).flatMap { case Some(row) => Future.successful(row) case None => Future.failed(RackException(s"Rack not found with id: ${rack.id}")) } recoverWith { // 捕获Repository调用时的异常,转换成自定义异常 case e: Exception => Future.failed(RackException(s"Error on select Rack: ${e.getMessage}")) } // 现在直接用rackRow.id,不需要.get seqGpuRow <- gpuRepository.getByRack(rackRow.id) recoverWith { case e: Exception => Future.failed(GpuException(s"Error on select Gpu's from Rack: ${e.getMessage}")) } } yield (rackRow, seqGpuRow) println(2) val result = Await.result(futureResult, 20 seconds) // 这里rackRow已经是确定存在的,不需要再处理None情况 val (rackRow, seqGpuRow) = result // 后续业务逻辑... Ok } catch { case re: RackException => BadRequest(s"Rack Exception: ${re.getMessage}") case ge: GpuException => BadRequest(s"Gpu Exception: ${ge.getMessage}") case e: Exception => BadRequest(s"Exception found: ${e.getMessage}") }
额外提醒
在Scala里,永远不要随意调用.get处理Option,除非你能100%保证它不会是None。尤其是在Future异步场景下,.get抛出的异常会被包装成通用的Exception,不仅难以定位问题,还会破坏你自定义异常的捕获逻辑。优先用模式匹配、flatMap、fold这类安全的方式处理Option。
内容的提问来源于stack exchange,提问作者Felipe

