通过Scala期货链传播错误



考虑一系列期货,每个期货都返回任一[Status,Resp]。您将如何通过使用Future而不是非此即彼的用于理解的来传播错误状态代码?

下面的代码不起作用,因为解析异常没有被最后一个未来的.recover捕获

用例是Scala Play ActionRefiners,它返回Future[Enore[Status,TRequest[A]]]。

def parseId(id: String):Future[Int] = {
Future.successful(Integer.parseInt(id))
}
def getItem(id: Int)(implicit ec: ExecutionContext): Future[Either[Status, String]] =
Future(Some("dummy res from db " + id)).transformWith {
case Success(opt) => opt match {
case Some(item) => Future.successful(Right(item))
case _ => Future.successful(Left(NotFound))
}
case Failure(_) => Future.successful(Left(InternalServerError))
}
(for {
id <- parseId("bad request")
resp <- getItem(id)
} yield resp).recover {
case _:NumberFormatException => Left(BadRequest)
}

我可以将.recover移到parseId,但这使得理解变得非常丑陋——必须处理中间中的任一[Status,id]

def parseId(id: String):Future[Either[Status, Int]] = {
Future.successful(Right(Integer.parseInt(id))).recover {
case _:NumberFormatException => Left(BadRequest)
}
}

您的异常没有被捕获,因为您没有在Future内部抛出它:如果Future.successful对您给它的表达式的结果感到满意,则立即,如果它抛出异常,则在当前线程上执行。

尝试删除.successful:Future(id.toInt)将执行您想要的操作。

此外,我建议去掉所有的Either:它们被高度高估/过度使用,尤其是在Future的上下文中(无论如何,它们已经将结果封装到Try中(,只会使代码变得更复杂、可读性更低,而不会带来太多好处。

case class FailureReason(status: Status) 
extends Exception(status.toString)
def notFound() = throw FailureReason(NotFound)
def internalError() = throw FailureReason(InternalError)
def badRequest() = throw FailureReason(BadRequest)

def parseId(id: String):Future[Int] = Future(id.toInt)
def getItem(id: Int): Future[String] = Future(Some("dummy"))
.map { _.getOrElse(notFound) }
.recover { _ => internalError }
// this is the same as your for-comprehension, just looking less ugly imo :) 
parseId("foo").flatMap(getItem).recover { 
case _: NumberFormatException => badRequest()
}
// if you still want `Either` in the end for some reason: 
.map(Right.apply[Status, String])
.recover { 
case _: NumberFormatException => Left(BadRequest) // no need for the first recover above if you do this
case FailureReason(status) => Left(status)
}

最新更新