我正在进行3个数据库查询,每个查询返回一个Future
。我正在尝试使用for
理解来解决Future
,但是似乎我在if
for
每个查询取决于上一个查询的结果。我寻找token
,如果找到,我寻找user
,发现它,我更新user
。每个数据库查询都返回一个Future[Option]]
,我想我可以根据前一个查询返回Some
还是None
来执行下一个查询。我为此使用isDefined
。但是,当我为无效令牌运行代码时,代码[NoSuchElementException: None.get]
出现错误userOption:Option[User]<-userRepo.findUser(tokenOption.get.loginInfo); if tokenOption.isDefined
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
}
}
答案 0 :(得分:0)
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
...
您会看到tokenOption.get
在withFilter
之前被调用。这些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$))
}
}
答案 1 :(得分:0)
我不确定您为什么惊讶并发现None.get
带有无效令牌的错误:如果令牌无效,则tokenOption
为None
,因此下一条语句{{ 1}}将完全由于此错误而失败。
您要在要短路的语句之前而不是在语句后执行“后卫”:
tokenOption.get
但这最终还是会失败,因为for {
foo <- bar if foo.isDefined
baz <- foo.get
} yield baz
毫无用处(此技巧适用于yield
或Options
等,但是Lists
将结束如果谓词不满足,则会失败,没有其他选择)。
避免此类错误的一般规则是切勿在{{1}}(或Future.withFilter
)上使用.get
。另外,切勿在{{1}}上使用Option
,在Try
上使用.head
,等等。
这是一种(几乎)惯用的方式来编写您想要的内容:
List
注意,我说这是“几乎”惯用的,因为在scala中抛出异常是不受欢迎的。纯粹主义者会反对它,并建议使用.apply
之类的东西。或带有偏见的Map
,或者利用第三方库,例如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" }
或Try
,它们提供了用于处理{{1 }}(即Either
)。
但是我不建议现在就开始讨论。在开始使用高级的东西之前,您应该对基本的“香草” scala有足够的了解,以免最终导致完全无法理解的东西。
您还可以以完全惯用的方式(不使用异常)编写不同的内容,例如:
cats
这是“纯”的,但重复性更高,所以我的个人偏爱是第一种。
最后,您可以消除我的scalaz
麻烦,而直接处理Future
异常。这将是最短的,但即使按我的口味,还是有点讨厌(如果某些下游代码由于错误而抛出此异常怎么办?):
Option
我真的虽然不建议使用最新版本,尽管它是最短的版本,并且可以说是可读性最高的版本(您甚至可以用理解的方式重写它,以使其看起来更好)。 。使用OptionT
通常被认为是“代码异味”,几乎绝不是一件好事。
答案 2 :(得分:0)
受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
}
}