我正在进行3个数据库查询,每个查询都返回一个Future。我试图使用for理解来解析Future%s,但是我似乎没有在for中正确使用if。
每个查询都依赖于前一个查询的结果。我查找一个token,如果找到了,我查找user,它找到了,我更新user。每个数据库查询都返回一个Future[Option]],我认为我可以根据前一个查询返回的是Some还是None来有条件地执行下一个查询。为此,我使用了isDefined。但是,当我为一个无效的令牌运行代码时,我得到了代码userOption:Option[User]<-userRepo.findUser(tokenOption.get.loginInfo); if tokenOption.isDefined的错误[NoSuchElementException: None.get]
def verifyUser(token:String) = Action.async {
implicit request => {
val result:Future[Result] = for{
//generator 1 - get token from database
tokenOption:Option[UserToken] <- userTokenRepo.find(UserTokenKey(UUID.fromString(token)))
//generator2. found token, look for corresponding user to which the token belongs
userOption:Option[User] <- userRepo.findUser(tokenOption.get.loginInfo); if tokenOption.isDefined
//generator 3. found user and token. Update profile
modifiedUser:Option[User] <- confirmSignupforUser(userOption.get); if userOption.isDefined
} yield
{ //check if we have user and token and modified user here. If any is missing, return error else success
if(tokenOption.isDefined && userOption.isDefined && modifiedUser.isDefined)
Redirect("http://localhost:9000/home"+";signup=success")//TODOM - pick from config
else
if(tokenOption.isEmpty)
Redirect("http://localhost:9000/home"+";signup=error")//TODOM - pick from config
else if(userOption.isEmpty)
Redirect("http://localhost:9000/home"+";signup=error")//TODOM - pick from config
else if(modifiedUser.isEmpty)
Redirect("http://localhost:9000/home"+";signup=error")//TODOM - pick from config
else //this shouldn't happen. Unexpected
Redirect("http://localhost:9000/home"+";signup=error")//TODOM - pick from config
}
result
}
}发布于 2018-09-18 20:00:09
TL;DR
考虑使用OptionT https://typelevel.org/cats/datatypes/optiont.html
看一看我的柔和实现:
来自https://scastie.scala-lang.org/hsXXtRAFRrGpMO1Jl1Li7A
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Await.result
import scala.concurrent.duration._
import scala.language.postfixOps
type UserToken = String
type User = String
def fromToken(token: String): Future[Option[UserToken]] = Future.successful(None)
def findUser(userToken: UserToken): Future[Option[User]] = Future.successful(None)
def modify(user: User): Future[Option[User]] = Future.successful(None)
def verifyUser(token: String) = {
val result = for {
tokenOption: Option[UserToken] <- fromToken(token) //generator 1 - get token from database
userOption: Option[User] <- findUser(tokenOption.get);
if tokenOption.isDefined //generator2. found token, look for corresponding user to which the token belongs
modifiedUser: Option[User] <- modify(userOption.get);
if userOption.isDefined //generator 3. found user and token. Update profile
} yield { //check if we have user and token and modified user here. If any is missing, return error else success
if (tokenOption.isDefined && userOption.isDefined && modifiedUser.isDefined)
println("happy")
else
println("sad")
}
result
}
result(verifyUser("hello"), 1 second)我使用了以下编译时间标志the last one is important
scalacOptions ++= Seq(
"-deprecation",
"-encoding", "UTF-8",
"-feature",
"-unchecked",
"-Xprint:typer"
)让我们关注编译输出的这一行:
(((tokenOption: Option[Playground.this.UserToken]) => Playground.this.findUser(tokenOption.get).
withFilter(((check$ifrefutable$2: Option[Playground.this.User]) => (check$ifrefutable$2: Option[Playground.this.User] @unchecked) match {
case (userOption @ (_: Option[Playground.this.User])) => true
case _ => false
...您可以看到,在调用withFilter之前调用了tokenOption.get。这些get是您得到的异常的来源。
编译的几乎完整的输出是:
[[syntax trees at end of typer]] // main.scala
....
import scala.concurrent.Future;
import scala.concurrent.ExecutionContext.Implicits.global;
import scala.concurrent.Await.result;
import scala.concurrent.duration._;
import scala.language.postfixOps;
type UserToken = String;
type User = String;
def fromToken(token: String): scala.concurrent.Future[Option[Playground.this.UserToken]] = scala.concurrent.Future.successful[None.type](scala.None);
def findUser(userToken: Playground.this.UserToken): scala.concurrent.Future[Option[Playground.this.User]] = scala.concurrent.Future.successful[None.type](scala.None);
def modify(user: Playground.this.User): scala.concurrent.Future[Option[Playground.this.User]] = scala.concurrent.Future.successful[None.type](scala.None);
def verifyUser(token: String): scala.concurrent.Future[Unit] = {
val result: scala.concurrent.Future[Unit] = Playground.this.fromToken(token).withFilter(((check$ifrefutable$1: Option[Playground.this.UserToken]) => (check$ifrefutable$1: Option[Playground.this.UserToken] @unchecked) match {
case (tokenOption @ (_: Option[Playground.this.UserToken])) => true
case _ => false
}))(scala.concurrent.ExecutionContext.Implicits.global).flatMap[Unit](((tokenOption: Option[Playground.this.UserToken]) => Playground.this.findUser(tokenOption.get).withFilter(((check$ifrefutable$2: Option[Playground.this.User]) => (check$ifrefutable$2: Option[Playground.this.User] @unchecked) match {
case (userOption @ (_: Option[Playground.this.User])) => true
case _ => false
}))(scala.concurrent.ExecutionContext.Implicits.global).withFilter(((userOption: Option[Playground.this.User]) => tokenOption.isDefined))(scala.concurrent.ExecutionContext.Implicits.global).flatMap[Unit](((userOption: Option[Playground.this.User]) => Playground.this.modify(userOption.get).withFilter(((check$ifrefutable$3: Option[Playground.this.User]) => (check$ifrefutable$3: Option[Playground.this.User] @unchecked) match {
case (modifiedUser @ (_: Option[Playground.this.User])) => true
case _ => false
}))(scala.concurrent.ExecutionContext.Implicits.global).withFilter(((modifiedUser: Option[Playground.this.User]) => userOption.isDefined))(scala.concurrent.ExecutionContext.Implicits.global).map[Unit](((modifiedUser: Option[Playground.this.User]) => if (tokenOption.isDefined.&&(userOption.isDefined).&&(modifiedUser.isDefined))
scala.Predef.println("happy")
else
scala.Predef.println("sad")))(scala.concurrent.ExecutionContext.Implicits.global)))(scala.concurrent.ExecutionContext.Implicits.global)))(scala.concurrent.ExecutionContext.Implicits.global);
result
};
scala.Predef.locally[Unit]({
val $t: Unit = scala.concurrent.Await.result[Unit](Playground.this.verifyUser("hello"), scala.concurrent.duration.`package`.DurationInt(1).second);
Playground.this.instrumentationMap$.update(com.olegych.scastie.api.Position.apply(1199, 1236), com.olegych.scastie.api.runtime.Runtime.render[Unit]($t)((ClassTag.Unit: scala.reflect.ClassTag[Unit])));
$t
})
};
object Main extends scala.AnyRef {
def <init>(): Main.type = {
Main.super.<init>();
()
};
private[this] val playground: Playground = new Playground();
<stable> <accessor> def playground: Playground = Main.this.playground;
def main(args: Array[String]): Unit = scala.Predef.println(com.olegych.scastie.api.runtime.Runtime.write(Main.this.playground.instrumentations$))
}
}发布于 2018-09-18 20:09:53
我不知道为什么您会惊讶地看到令牌无效的None.get出现错误:如果令牌无效,则tokenOption为None,因此,下一条语句tokenOption.get将失败,并显示以下错误。
你想让“卫士”在你想要缩短的语句之前执行,而不是在它之后:
for {
foo <- bar if foo.isDefined
baz <- foo.get
} yield baz但这最终都会失败,因为没有什么可以使用yield (这个技巧适用于Options或Lists等,但如果不满足predicate,Future.withFilter将以失败告终,没有其他选择)。
避免此类错误的一般规则是永远不要在Option (或Try__)上使用.get。另外,永远不要在List上使用.head,在Map上使用.apply,等等。
这里有一种(几乎)惯用的方式来编写你想要的东西:
case object Error extends RuntimeException("")
userTokenRepo
.find(UserTokenKey(UUID.fromString(token)))
.map { _.getOrElse(throw Error)
.flatMap { userRepo.find(_.loginInfo) }
.map { _.getOrElse(throw Error) }
.flatMap(confirmSignupForUser)
.map { _.getOrElse(throw Error) }
.map { _ => "success") }
.recover { case Error => "error" }
.map { result => Redirect(s"http://localhost:9000/home;signup=$result" }注意,我说这“几乎”是惯用的,因为在scala中抛出异常是不受欢迎的。一个纯粹主义者会反对它,并建议使用像Try这样的东西。或者是偏向的Either,或者使用第三方库,如cats或scalaz,这些库提供了额外的工具来使用Option的Future(即OptionT)。
但我不建议现在就开始讨论这个问题。在开始使用高级scala之前,您应该对基本的“普通”scala有足够的了解,以避免出现一些完全无法理解的东西。
您也可以以完全惯用的方式(不使用异常)以不同的方式编写此代码,如下所示:
userTokenRepo.find(UserTokenKey(UUID.fromString(token)))
.flatMap {
case Some(token) => userRepo.find(token.loginInfo)
case None => Future.successful(None)
}.flatMap {
case Some(user) => confirmSignupForUser(user)
case None => Future.successful(None)
}.map {
case Some(_) => "success"
case None => "error"
}.map { result =>
Redirect(s"http://localhost:9000/home;signup=$result"
}这更“纯粹”,但更具重复性,所以我个人的偏好是第一个变体。
最后,您可以不再使用Error,只需直接处理NoSuchElement异常即可。这将是最短的,但即使对我来说也有点麻烦(如果一些下游代码因为bug而抛出这个异常呢?):
userTokenRepo
.find(UserTokenKey(UUID.fromString(token)))
.flatMap { userRepo.find(_.get.loginInfo) }
.flatMap(confirmSignupForUser(_.get))
.map { _.get }
.map { _ => "success") }
.recover { case _: NoSuchElementException => "error" }
.map { result =>
Redirect(s"http://localhost:9000/home;signup=$result"
}我真的不推荐最后一个版本,尽管它是最短的,而且可以说是最具可读性的版本(你甚至可以重写它,使它看起来更好)。使用Option.get通常被认为是“代码气味”,而且几乎从来都不是一件好事。
发布于 2018-09-18 22:24:33
由How to best handle Future.filter predicate is not satisfied type errors激发
我像下面这样重写。当代码正常工作时,我很想知道我这样做的方式是否正确(函数式!)。它看起来很好吗?
def verifyUser(token:String) = Action.async {
implicit request => {
println("verifyUser action called with token: " + token) //TODOM - add proper handling and response
val result:Future[Result] = for{tokenOption:Option[UserToken] <- userTokenRepo.find(UserTokenKey(UUID.fromString(token))) //generator 1 - get token from database
userOption:Option[User] <- if (tokenOption.isDefined) userRepo.findUser(tokenOption.get.loginInfo) else Future.successful(None) //generator2. found token, look for corresponding user to which the token belongs
modifiedUser:Option[User] <- if (userOption.isDefined) confirmSignupforUser(userOption.get) else Future.successful(None) //generator 3. found user and token. Update profile
deletedToken:Option[UserTokenKey] <- if(modifiedUser.isDefined) userTokenRepo.remove(UserTokenKey(UUID.fromString(token))) else Future.successful(None)
}
yield { //check if we have user and token and modified user here. If any is missing, return error else success
println("db query results tokenOption: "+tokenOption+", userOption: "+userOption+" : modifiedUserOption: "+modifiedUser+", deletedToken: "+deletedToken)
if(tokenOption.isDefined && userOption.isDefined && modifiedUser.isDefined && deletedToken.isDefined)
Redirect("http://localhost:9000/home"+";signup=success")//TODOM - pick from config
else
if(tokenOption.isEmpty)
Redirect("http://localhost:9000/home"+";signup=error")//TODOM - pick from config
else if(userOption.isEmpty)
Redirect("http://localhost:9000/home"+";signup=error")//TODOM - pick from config
else if(modifiedUser.isEmpty)
Redirect("http://localhost:9000/home"+";signup=error")//TODOM - pick from config
else //this shouldn't happen. Unexpected
Redirect("http://localhost:9000/home"+";signup=error")//TODOM - pick from config
}
result
}
}https://stackoverflow.com/questions/52385618
复制相似问题