Scalaz:如何链接 Future[List[A]]、Option[A] 和 Future[Option[A]] 并报告个别错误
Scalaz: How to Chain Future[List[A]], Option[A] and Future[Option[A]] And Report Individual Error
(for {
orderId <- ListT(extractParamFromHttp(request).toList) // extractParamFromHttp(request) returns Option[Long]
order <- ListT(serviceA.retrieve(orderId).map(_.toList)) // serviceA.retrieve(...) returns Future[Option[Order]]
items <- ListT(serviceB.retrieve(order.id).map(_.toList)) // serviceB.retrieve(...) returns Future[Seq[OrderItem]]
} yield items).map(...) // convert items to JSON and return as Future[Result]
上面的代码有效,如果输入不正确或缺少输入,当其中任何一个无法获取信息时,它 returns 为 Nil。我想要用更具体的错误消息而不是空列表来响应。例如,如果未找到 HTTP 请求中的参数。我想告诉发件人缺少 HTTP 参数或找不到给定的订单 ID。使用 EitherT 或 Either 会有帮助吗?我该怎么办?谢谢
更新 1
我使用 Scalaz EitherT
和 Either
(来自 Validation
的提示)解决了它,并在此处粘贴了我的解决方案并稍加改动,
import scalaz._
import Scalaz._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
case class Order(id: Long, desc: String)
case class Vendor(id: Long, name: String)
def extractHeader(valid: Boolean): Option[String] = if (valid) Some("bob") else None
def retrieveVendor(username: String, found: Boolean): Future[Option[Vendor]] =
if (found) Future.successful(Some(Vendor(103, "Bob Enterprise"))) else Future.successful(None)
def retrieveOrders(vendorId: Long, found: Boolean): Future[List[Order]] =
if (found) Future.successful(List(Order(1, "Product 1"), Order(2, "Product 2"))) else Future.successful(List.empty[Order])
def output(result: Future[\/[String, AnyRef]]) = result.foreach {
case -\/(s) => println(s"Error: $s")
case \/-(ys @ x :: xs) => println(ys)
case \/-(s) => println(s)
}
def orders(valid: Boolean, userFound: Boolean, vendorFound: Boolean) = for {
username <- EitherT(Future.successful(extractHeader(valid) \/> "Bad headers")) // Option[String]
vendor <- EitherT(retrieveVendor(username, userFound).map(_ \/> "Bad username")) // Future[Option[Vendor]]
orders <- EitherT(retrieveOrders(vendor.id, vendorFound).map { // Future[Seq[Order]]
case Nil => "Bad vendor".left
case xs => xs.right
})
} yield orders
output(orders(false,false, true).run)
参考非Scalaz解决方案的答案
更新 2
对于此解决方案或更详尽的解决方案的应用形式(如果需要),请参阅here
我建议你扔scalaz,它对这个用例没有任何价值,只会让你更困惑。
未能保持简单通常是此类问题的原因。
def ifExists[T, R](opt: Option[T], err: String)(f: T => Future[R]) =
opt.fold(Future.failed(new IllegalArgumentException(err))(f)
for(
order <- ifExists(extractParamFromHttp(request), "Missing param) {
serviceA.retrieve(_)
}
items <- ifExists(order, "Order not found") { serviceB.retrieve(_) }
result <- ...
) yield result
此 returns Future[Result]
,完成后将包含最终结果或指示失败原因的异常。
然后调用者可以使用 future.handle...
检查异常或使用 future.transform
或 future.onComplete
处理成功和失败
(for {
orderId <- ListT(extractParamFromHttp(request).toList) // extractParamFromHttp(request) returns Option[Long]
order <- ListT(serviceA.retrieve(orderId).map(_.toList)) // serviceA.retrieve(...) returns Future[Option[Order]]
items <- ListT(serviceB.retrieve(order.id).map(_.toList)) // serviceB.retrieve(...) returns Future[Seq[OrderItem]]
} yield items).map(...) // convert items to JSON and return as Future[Result]
上面的代码有效,如果输入不正确或缺少输入,当其中任何一个无法获取信息时,它 returns 为 Nil。我想要用更具体的错误消息而不是空列表来响应。例如,如果未找到 HTTP 请求中的参数。我想告诉发件人缺少 HTTP 参数或找不到给定的订单 ID。使用 EitherT 或 Either 会有帮助吗?我该怎么办?谢谢
更新 1
我使用 Scalaz EitherT
和 Either
(来自 Validation
的提示)解决了它,并在此处粘贴了我的解决方案并稍加改动,
import scalaz._
import Scalaz._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
case class Order(id: Long, desc: String)
case class Vendor(id: Long, name: String)
def extractHeader(valid: Boolean): Option[String] = if (valid) Some("bob") else None
def retrieveVendor(username: String, found: Boolean): Future[Option[Vendor]] =
if (found) Future.successful(Some(Vendor(103, "Bob Enterprise"))) else Future.successful(None)
def retrieveOrders(vendorId: Long, found: Boolean): Future[List[Order]] =
if (found) Future.successful(List(Order(1, "Product 1"), Order(2, "Product 2"))) else Future.successful(List.empty[Order])
def output(result: Future[\/[String, AnyRef]]) = result.foreach {
case -\/(s) => println(s"Error: $s")
case \/-(ys @ x :: xs) => println(ys)
case \/-(s) => println(s)
}
def orders(valid: Boolean, userFound: Boolean, vendorFound: Boolean) = for {
username <- EitherT(Future.successful(extractHeader(valid) \/> "Bad headers")) // Option[String]
vendor <- EitherT(retrieveVendor(username, userFound).map(_ \/> "Bad username")) // Future[Option[Vendor]]
orders <- EitherT(retrieveOrders(vendor.id, vendorFound).map { // Future[Seq[Order]]
case Nil => "Bad vendor".left
case xs => xs.right
})
} yield orders
output(orders(false,false, true).run)
参考非Scalaz解决方案的答案
更新 2
对于此解决方案或更详尽的解决方案的应用形式(如果需要),请参阅here
我建议你扔scalaz,它对这个用例没有任何价值,只会让你更困惑。 未能保持简单通常是此类问题的原因。
def ifExists[T, R](opt: Option[T], err: String)(f: T => Future[R]) =
opt.fold(Future.failed(new IllegalArgumentException(err))(f)
for(
order <- ifExists(extractParamFromHttp(request), "Missing param) {
serviceA.retrieve(_)
}
items <- ifExists(order, "Order not found") { serviceB.retrieve(_) }
result <- ...
) yield result
此 returns Future[Result]
,完成后将包含最终结果或指示失败原因的异常。
然后调用者可以使用 future.handle...
检查异常或使用 future.transform
或 future.onComplete