我有一个类型Future[Option[String]]
的集合,我将其映射到返回Future[Option[Profile]]
的函数,但这创建了一个返回类型Future[Option[Future[Option[Profile]]]]
queryProfile
因为返回类型是"未来[选项[配置文件]]"
val users: Future[Option[User]] = someQuery
val email: Future[Option[String]] = users map(opSL => opSL map(_.email) )
val userProfile = email map {opE => opE map {E => queryProfile(E)}}
我需要在不解压缩所有这些关卡的情况下使用包含在val userProfile
深处的Profile
对象,使用flatMap
或"扁平化"的正确方法是什么,或者有更好的方法
你可以得到一个"部分未来",如下所示:
val maybeProfile: Future[Profile] = users
.collect { case Some(u) => u.email }
.flatMap { email => queryProfile(email) }
.collect { case Some(p) => p }
现在maybeProfile
包含(完全"裸"/未包装)Profile
实例,但前提是它能够找到它。您可以像往常一样.map
它来使用它做其他事情,这将以通常的方式工作。
如果您想阻止并等待完成,则必须在某个时候处理丢失的情况。例如:
val optionalProfile: Option[Profile] = Await.result(
maybeProfile
.map { p => Some(p) } // Or just skip the last `collect` above
.recover { case _:NoSuchElementException => None },
1 seconds
)
如果你对只有Future[Option[Profile]]
感到满意,并且更愿意有"解开"的魔法,并将丢失的案例本地化在一个地方,你可以像这样把上面的两个片段放在一起:
val maybeProfile: Future[Option[Profile]] = users
.collect { case Some(u) => u.email }
.flatMap { email => queryProfile(email) }
.recover { case _:NoSuchElementException => None }
或者像另一个答案建议的那样使用Option.fold
:
val maybeProfile: Future[Option[Profile]] = users
.map { _.map(_.email) }
.flatMap { _.fold[Future[Option[Profile]]](Future.successful(None))(queryProfile) }
就个人而言,我发现最后一个选项的可读性较差。
就我个人而言,我认为像 scalaz/cats 提供的 OptionT 这样的 monad 变压器将是最干净的方法:
val users = OptionT[Future,User](someQuery)
def queryProfile(email:String) : OptionT[Future,Profile] = ...
for {
u <- users
p <- queryProfile(u.email)
} yield p
我只是创建一个这样的帮助程序方法:
private def resolveProfile(optEmail: Option[String]): Future[Option[Profile] =
optEmail.fold(Future.successful(None)) { email =>
queryProfile(email).map(Some(_))
}
然后,您可以像这样flatMap
原始email
未来:
val userProfile = email.flatMap(resolveProfile)