Scala:如何获得 Future 的结果
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/22900197/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Scala: How to get the result of a Future
提问by j3d
I've a method that returns a Futurelike this...
我有一个返回Future这样的方法......
def isTokenExpired(token: String): Future[Boolean] = {
...
}
... and then I've another method that invokes isTokenExpiredthat returns a Booleanlike this:
...然后我有另一种调用方法,isTokenExpired它返回Boolean这样的:
def isExpired(token: String): Boolean = {
var result = true
isTokenExpired(token).onComplete {
case Success(r) => result = r
case Failure(_) => result = true
}
result
}
Is there a better way to write the isExpiredmethod?
有没有更好的方法来编写isExpired方法?
EDIT
编辑
As requested by EECOLOR, let me provide you with more details. For my Play application I've implemented an authorization mechanism based on JSON Web Token (jwt). All the claims are contained in the jwt except the expiration time, which is stored in a MongoDB collection. Here below is a summary of how my Tokenclass looks like:
根据 ECOLOR 的要求,让我为您提供更多详细信息。对于我的 Play 应用程序,我实现了基于 JSON Web Token (jwt) 的授权机制。所有声明都包含在 jwt 中,除了过期时间,它存储在 MongoDB 集合中。以下是我的Token班级的总结:
class Token {
...
def id: String = { ... }
def issueTime: LocalDateTime = { ... }
def issuer: String = { ... }
...
def isValid: Boolean = { ... }
def isExpired: Boolean = { /* uses ReactiveMongo to access MongoDB */ }
}
As you can see, all the jwt properties are self-contained except expiration info. Method isExpireduses ReactiveMongo, which always returns a Future. To make things even more complex, I use this jwt in a customized Actionlike this:
如您所见,除过期信息外,所有 jwt 属性都是独立的。方法isExpired使用 ReactiveMongo,它总是返回一个Future. 为了使事情变得更加复杂,我在这样的自定义中使用了这个 jwt Action:
class SecuredAction[T <: Controller] private(private val methodName: String)
extends ActionBuilder[ApiRequest] {
...
def invokeBlock[A](request: Request[A], block: (ApiRequest[A]) => Future[SimpleResult]) = {{
request.headers.get(HeaderNames.AUTHORIZATION) match {
case Some(header) => s"""$AuthType (.*)""".r.unapplySeq(header).map(_.head.trim)
case _ => None
}} match {
case Some(tokenString) => {
val token = Token(tokenString)
if (!token.isValid) {
Logger.warn(s"request ${request.uri} not authorized: token ${token.id} has been tampered")
Future.successful(Unauthorized(AuthErrors.authenticationViolated(token.subject)(request).asJson))
} else if (token.isExpired) {
Logger.debug(s"request ${request.uri} not authorized: token ${token.id} has expired")
Future.successful(Unauthorized(AuthErrors.authenticationExpired(token.subject)(request).asJson))
} else if (!isAuthorized(token)) {
Logger.info(s"request ${request.uri} not authorized: required claims not defined for account ${token.subject}")
Future.successful(Forbidden(AuthErrors.requestNotAuthorized(token.subject)(request).asJson))
} else {
Logger.debug(s"request ${request.uri} authorized for account ${token.subject}")
block(new ApiRequest(token, request))
}
}
case _ => {
Logger.debug(s"request ${request.uri} not authenticated")
Future.successful(Unauthorized(
AuthErrors.requestNotAuthenticated()(request).asJson
).withHeaders(HeaderNames.WWW_AUTHENTICATE -> AuthType))
}
}
}
As you can see, I need to return a Future[play.mvc.results.Result], not a Future[Boolean]as would return isExpiredif I used Future.map. Do you get to point?
如您所见,我需要返回 a Future[play.mvc.results.Result],而不是Future[Boolean]像isExpired使用Future.map. 你明白了吗?
采纳答案by EECOLOR
The function you wrote will not work as you would think. It would (likely) first return trueand later set the resultvariable.
你写的函数不会像你想象的那样工作。它会(可能)首先返回true然后设置result变量。
Normally you would do something like this:
通常你会做这样的事情:
isTokenExpired(token).map { result =>
// do stuff
}
In a framework like Play you would map the Futureto an http response and give Play back a Future[SimpleResult]. Play knows how to handle Futureresults.
在像 Play 这样的框架中,您可以将 映射Future到 http 响应并给 Play 回一个Future[SimpleResult]. Play 知道如何处理Future结果。
In general it's recommended you do not wait for a Futureto complete in production code, but work with the values in the Futureand let the framework you are using handle the result.
一般来说,建议您不要Future在生产代码中等待 a完成,而是使用 中的值Future并让您使用的框架处理结果。
In tests it might come in handy to wait for a result, you can do that like this:
在测试中等待结果可能会派上用场,你可以这样做:
Await.result(someFuture, 5.seconds)
Edit
编辑
I would probably extract the construction of a token so that I end up with a Future[Token]. That allows me to more easily compose things. It also allows me to create code that has a better architecture and is easier to test.
我可能会提取令牌的构造,以便最终得到Future[Token]. 这让我可以更轻松地撰写内容。它还允许我创建具有更好架构且更易于测试的代码。
I would probably break down the code into more smaller methods, but the example below gives you an idea of the direction I would take.
我可能会将代码分解为更小的方法,但下面的示例让您了解我将采取的方向。
class TokenService(connection: MongoConnection) {
def tokenFor(tokenString: String): Future[Token] = ???
}
class SecuredAction(tokenService: TokenService) extends
ActionBuilder[ApiRequest] {
import play.api.libs.concurrent.Execution.Implicits._
def invokeBlock[A](request: Request[A], block: (ApiRequest[A]) => Future[SimpleResult]) =
extractTokenFrom(request) match {
case Some(tokenString) => {
tokenService.tokenFor(tokenString) flatMap {
case token if (!token.isValid) =>
Logger.warn(s"request ${request.uri} not authorized: token ${token.id} has been tampered")
Future.successful(Unauthorized(AuthErrors.authenticationViolated(token.subject)(request).asJson))
case token if (token.isExpired) =>
Logger.debug(s"request ${request.uri} not authorized: token ${token.id} has expired")
Future.successful(Unauthorized(AuthErrors.authenticationExpired(token.subject)(request).asJson))
case token if (!token.isAuthorized) =>
Logger.info(s"request ${request.uri} not authorized: required claims not defined for account ${token.subject}")
Future.successful(Forbidden(AuthErrors.requestNotAuthorized(token.subject)(request).asJson))
case token =>
Logger.debug(s"request ${request.uri} authorized for account ${token.subject}")
block(new ApiRequest(token, request))
}
}
case _ =>
Logger.debug(s"request ${request.uri} not authenticated")
Future.successful(Unauthorized(
AuthErrors.requestNotAuthenticated()(request).asJson).withHeaders(HeaderNames.WWW_AUTHENTICATE -> AuthType))
}
val AuthType = "MyAuthType"
val TokenHeader = s"""$AuthType (.*)""".r
def extractTokenFrom(request: RequestHeader) = {
val authorizationHeader = request.headers.get(HeaderNames.AUTHORIZATION)
authorizationHeader flatMap {
case TokenHeader(token) => Some(token.trim)
case _ => None
}
}
}
回答by user3504561
One solution would be to make future current by literallywaiting for the result forever:
一个解决办法是通过使未来电流从字面上等待结果永远:
Await.result(futureResult, Duration.Inf)
Waiting forever transforms a Future[Value]into a Valuewhich is what you want.
永远等待将 aFuture[Value]转换为 aValue这就是您想要的。
That's how I love to comment this move in my code...
这就是我喜欢在我的代码中评论这个举动的方式......
// wait forever to make future current (?)
Obs.I infinite duration onlyin my ScalaTest. For production, the waiting period should be tuned as done by @EECOLOR.

