[英]Scala's for comprehension for Futures and Options
我最近阅读了Manuel Bernhardt的新书Reactive Web Applications 。 在他的书中,他指出,斯卡拉开发人员不应该使用.get
检索一个可选值。
我想接受他的建议,但我正在努力避免.get
用于对期货的理解。
假设我有以下代码:
for {
avatarUrl <- avatarService.retrieve(email)
user <- accountService.save(Account(profiles = List(profile.copy(avatarUrl = avatarUrl)))
userId <- user.id
_ <- accountTokenService.save(AccountToken.create(userId, email))
} yield {
Logger.info("Foo bar")
}
通常,我会使用AccountToken.create(user.id.get, email)
而不是AccountToken.create(userId, email)
。 但是,当试图避免这种不良做法时,我得到以下异常:
[error] found : Option[Nothing]
[error] required: scala.concurrent.Future[?]
[error] userId <- user.id
[error] ^
我怎么解决这个问题?
如果你真的想for
理解,你必须将它分成几个for
s,其中每个都使用相同的monad类型:
for {
avatarUrl <- avatarService.retrieve(email)
user <- accountService.save(Account(profiles = List(profile.copy(avatarUrl = avatarUrl)))
} yield for {
userId <- user.id
} yield for {
_ <- accountTokenService.save(AccountToken.create(userId, email))
}
另一个选择是完全避免Future[Option[T]]
并使用Future[T]
,它可以实现为Failure(e)
,其中e
是NoSuchElementException
只要你期望None
(在你的情况下,是accountService.save()
方法) :
def saveWithoutOption(account: Account): Future[User] = {
this.save(account) map { userOpt =>
userOpt.getOrElse(throw new NoSuchElementException)
}
}
然后你会有:
(for {
avatarUrl <- avatarService.retrieve(email)
user <- accountService.saveWithoutOption(Account(profiles = List(profile.copy(avatarUrl = avatarUrl)))
_ <- accountTokenService.save(AccountToken.create(user.id, email))
} yield {
Logger.info("Foo bar")
}) recover {
case t: NoSuchElementException => Logger.error("boo")
}
回到map
/ flatMap
并介绍中间结果。
让我们退后一步,探索我们表达的意义:
Future
是“最终的价值(但可能会失败)” Option
是“可能是一个价值” Future[Option]
的语义是什么? 让我们探索价值观以获得一些直觉:
未来的[选项]
Success(Some(x))
=>好。 我们用x做的东西 Success(None)
=>完成但没有得到任何东西=>这可能是应用程序级错误 Failure(_)
=>出了点问题,所以我们没有价值 我们可以将Success(None)
Failure(SomeApplicationException)
为Failure(SomeApplicationException)
并且无需单独处理Option
。
为此,我们可以定义一个泛型函数将Option
转换为Future
并使用for-comprehension
来应用flattening。
def optionToFuture[T](opt:Option[T], ex: ()=>Exception):Future[T] = opt match {
case Some(v) => Future.successful(v)
case None => Future.failed(ex())
}
我们现在可以用一种for-comprehension
流利地表达我们的计算:
for {
avatarUrl <- avatarService.retrieve(email)
user <- accountService.save(Account(profiles = List(profile.copy(avatarUrl = avatarUrl)))
userId <- optionToFuture(user.id, () => new UserNotFoundException(email))
_ <- accountTokenService.save(AccountToken.create(userId, email))
} yield {
Logger.info("Foo bar")
}
选项为无时,通过使Future失败来停止Option
传播
当id为none并且中止时,将来失败
for {
....
accountOpt <-
user.id.map { id =>
Account.create(id, ...)
}.getOrElse {
Future.failed(new Exception("could not create account."))
}
...
} yield result
最好有一个自定义的例外
case class NoIdException(msg: String) extends Exception(msg)
只有当你确定选项是Some(x)
才会调用.get
on Option。否则.get
会抛出异常。
那就是使用.get
不是好习惯,因为它可能会导致代码中出现异常。
而不是.get
的好习惯使用getOrElse
。
您可以map
或flatMap
选项以访问内部值。
好的做法
val x: Option[Int] = giveMeOption()
x.getOrElse(defaultValue)
可以在这里使用
val x: Option[Int] = giveMeOption()
x.OrElse(Some(1)).get
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.