每个Scala Future最终都必须有Await.result调用吗



我正在努力学习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

还有filterforeach,以及两个最明显的mapflatmap。您可能想要使用的另一个组合子是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?

如果f1f2失败,则可以使用失败的投影,类似于以下内容:

val f1: Future[Int] = future {
  throw new RuntimeException("whoops")
}
for (t <- f1.failed) response.error(t)

Await ing

这里我们有Await.resultAwait.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)
  }

最新更新