我最近阅读了Manuel Bernhardt的新书Reactive Web Applications。在他的书中,他指出Scala开发人员应从不使用.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] ^
我该如何解决这个问题?
答案 0 :(得分:5)
如果确实想要使用for
理解,则必须将其与几个for
分开,其中每个都使用相同的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]
,只要您预期{{1},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
并介绍中间结果。
答案 1 :(得分:3)
让我们退后一步,探索我们表达的意义:
Future
“最终是一个值(但可能会失败)”Option
是“可能是值” Future[Option]
的语义是什么?让我们探索价值观以获得一些直觉:
<强>未来[选项] 强>
Success(Some(x))
=&gt;好。我们用x Success(None)
=&gt;完成但没有得到任何东西=&gt;这可能是应用程序级错误Failure(_)
=&gt;出了点问题,所以我们没有价值我们可以将Success(None)
展平为Failure(SomeApplicationException)
,并且无需单独处理Option
。
为此,我们可以定义一个通用函数将Option
转换为Future
并使用for-comprehension
来应用展平。
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")
}
答案 2 :(得分:1)
当选项为无时失败未来,停止Option
传播
当id为none且abort
时,将来失败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)
仅当您确定该选项为.get
时,才会在选项上调用Some(x)
,否则.get
将引发异常。
使用.get
这不是好习惯,因为它可能会导致代码中出现异常。
而不是.get
使用getOrElse
的好习惯。
您可以map
或flatMap
选项访问内部值。
良好做法
val x: Option[Int] = giveMeOption()
x.getOrElse(defaultValue)
可以在此处使用
val x: Option[Int] = giveMeOption()
x.OrElse(Some(1)).get