我正在努力学习Scala的未来。正如标题所示,在我看来,Await.result
最终必须在程序中的某个时刻被调用,才能将未来的值提取到Scala字段中。这是我迄今为止的代码。我在这里查询一个任意的uri,并将其映射到一个名为Address
的case类,假设这个Address
case类有一个字段final_balance
。
case class Address(final_balance:Double)
def getAddress(bitcoinAddress: String)(format: DataFormat): Future[Address] = {
val uri: String = "http://example.com"
val response: Future[HttpResponse] = (IO(Http) ? HttpRequest(GET, Uri(uri))).mapTo[HttpResponse]
implicit val addressJsonFormat = AddressJsonProtocol.addressFormat
val address: Future[Address] = response.flatMap { x =>
x match {
case HttpResponse(_, entity, _, _) =>
Future(entity.asString.parseJson.convertTo[Address])
}
}
address
}
def getFinalBalance(address: String): Double = {
val futureAddress: Future[Address] = QueryAddress.getAddress(address)(Json)
val finalBalance:Future[Double] = for { a <- futureAddress } yield a.final_balance
//block until the result can be determined
Await.result(finalBalance, 15.second)
}
这不正确吗?
这并不是从Future[A]
中获取或处理底层值/结果的唯一方法,还有一些其他方法需要考虑:
组合器
未来组合子的意义在于,在必须从最终未来中提取基础之前,能够将未来组合在一起。尽量拖延通常是个好主意。
有一个recover
组合子可以让你做一些事情来处理发生的异常:
f1 recover {
case e: ApiException => ...do something...
}
还有一个fallbackTo
组合子:
f1 fallbackTo f2 // (if result of `f1` is failure case, then execute `f2`)
现在,如果你想让第一个完成的未来成为返回的结果,你可以使用either
组合子,尽管它有一些有趣的特性,所以阅读文档并在REPL中使用它,以便更好地理解它:
f1 either f2
还有filter
、foreach
,以及两个最明显的map
和flatmap
。您可能想要使用的另一个组合子是zip
,它将两个期货的结果"压缩"为元组,然后您可以将apply
转换为数据/值构造函数。
andThen
也可以这样使用:
f1 andThen f2 // this defines order of execution (f1 and then f2 in this case)
映射(最常用的组合子IME)
是的,这并不能得到潜在的价值,但它可以用价值做的事情:
val fUsers: Future[Int] = future {
api.getConcurrentUsers
}
val fShouldCelebrate: Future[Boolean] = fUsers map { userCount =>
(userCount >= 1000000)
}
// pass fShouldCelebrate around and use a method for extracting the underlying out when
// absolutely needed.
通过回调
我不一定推荐这种方法,但它是您可以通过Future[A]
执行操作的方法之一。
一个例子是:
import scala.util.{Success, Failure}
// Not sure what a Response type is...but use your imagination :)
val response: Response = ???
val f: Future[List[String]] = future {
db.getRecentPosts
}
f onComplete {
case Success(posts) => for (p <- posts) response.render(p)
case Failure(t) => response.error("An error has occured: " + t.getMessage)
}
或者,我们可以将行为分为成功和失败两种情况:
import scala.util.{Success, Failure}
// Not sure what a Response type is...but use your imagination :)
val response: Response = ???
val f: Future[List[String]] = future {
db.getRecentPosts
}
f onSuccess {
case posts => for (p <- posts) response.render(p)
}
f onFailure {
case t => response.error("An error has occured: " + t.getMessage)
}
对于理解&预测
有时你有依赖的未来值,让我们看看这个例子:
val ticker: String = "XYZA" // hopefully not a real ticker
val f1: Future[TickerQuote] = future { exchange1.getQuote(ticker) }
val f2: Future[TickerQuote] = future { exchange2.getQuote(ticker) }
val trade = for {
q1 <- f1
q2 <- f2
if (q1 < q2)
} yield exchange1.buy(ticker, 100)
// obviously this is silly but hopefully you get the point?
如果f1
或f2
失败,则可以使用失败的投影,类似于以下内容:
val f1: Future[Int] = future {
throw new RuntimeException("whoops")
}
for (t <- f1.failed) response.error(t)
Await
ing
这里我们有Await.result
和Await.ready
阻塞调用来从Future中获取底层值。
我不建议您将自己限制在Future[A]
s。您应该看看Scalaz的Task[A]
构造,我发现它(对我来说)更加灵活、直观和可组合。
进一步阅读
- SIP-14-未来和承诺(注意:我根本没有涉及承诺,只是已经使用了ScalazConcurrent的
Task[A]
;)我一点也不固执己见。) - Scala新手指南第9部分:承诺&实践中的期货
- 使用
scalaz.concurrent.Task
的标量流
但最终它必须被称为,我们不能永远绕过未来,对吧?
我们可以——这就是未来之美:)
由于getFinalBalance
实际上并不需要final_balance,所以让我们更改代码以返回Future。
def getFinalBalance(address: String): Future[Double] = {
val futureAddress: Future[Address] = QueryAddress.getAddress(address)(Json)
for { a <- futureAddress } yield a.final_balance
}
现在我们又回到了拥有未来。如果我们想做一些有用的事情呢?假设我们只想把余额打印出来。我们可以使用具有以下签名的函数来描述这一点:
def printFinalBalance(balance: Future[Double]): Unit
要定义这个函数,我们可以使用Await.result,但我们不必这样做。我们可以使用onComplete注册一个回调函数,以便在Future完成时应用。
def printFinalBalance(fb: Future[Double]): Unit =
fb onComplete {
case Success(balance) => println("Balance: " + balance)
case Failure(e) => println("An error has occured: " + e.getMessage)
}