[英]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.