期货 - 地图与平面地图

时间:2015-07-26 19:42:58

标签: scala future

我已阅读有关mapflatMap的文档,我了解flatMap用于接受Future参数并返回另一个{{1}的操作}}。我不完全理解的是为什么我想这样做。举个例子:

  1. 用户点击我的网络服务要求“做事”
  2. 我下载了一个文件(速度很慢)
  3. 我处理文件(CPU密集型)
  4. 渲染结果
  5. 据我所知,我希望使用未来下载该文件,但我有两个选项可以重新处理它:

    Future

    val downloadFuture = Future { downloadFile }
    val processFuture = downloadFuture map { processFile }
    processFuture onSuccess { case r => renderResult(r) }
    

    通过添加调试语句(val downloadFuture = Future { // download the file } val processFuture = downloadFuture flatMap { Future { processFile } } processFuture onSuccess { case r => renderResult(r) } ),我发现在两种情况下,下载Thread.currentThread().getIdprocess都出现在同一个线程中(使用render)。

    我是否会使用ExecutionContext.Implicits.global简单地将flatMapdownloadFile分离,并确保processFile始终在processFile中运行,即使它未从Future映射{1}}?

4 个答案:

答案 0 :(得分:22)

  

确保processFile始终在Future中运行,即使它未从downloadFile映射?

是的,这是正确的。

但是大多数时候你不会直接使用Future { ... },你可以使用返回Future的函数(来自其他库或你自己的函数)。

想象一下以下功能:

def getFileNameFromDB{id: Int) : Future[String] = ???
def downloadFile(fileName: String) : Future[java.io.File] = ???
def processFile(file: java.io.File) : Future[ProcessResult] = ???

您可以使用flatMap组合它们:

val futResult: Future[ProcessResult] =
  getFileNameFromDB(1).flatMap( name =>
    downloadFile(name).flatMap( file =>
       processFile(file)
    )
  )

或使用for comprehension:

val futResult: Future[ProcessResult] =
  for {
    name <- getFileNameFromDB(1)
    file <- downloadFile(name)
    result <- processFile(file)
  } yield result

大多数情况下,您不会致电onSuccess(或onComplete)。通过使用这些函数之一,您可以注册一个回调函数,该函数将在Future完成时执行。

如果在我们的示例中您想要呈现文件处理的结果,则会返回Future[Result]之类的内容,而不是调用futResult.onSuccess(renderResult)。在最后一种情况下,您的返回类型为Unit,因此您无法真正返回某些内容。

在Play Framework中,这可能如下所示:

def giveMeAFile(id: Int) = Action.async {
  for {
    name <- getFileNameFromDB(1)
    file <- downloadFile(name)
    processed <- processFile(file)
  } yield Ok(processed.byteArray).as(processed.mimeType))
}

答案 1 :(得分:22)

如果您有未来,请说出Future[HttpResponse],并且您想要指定在该结果准备好后如何处理该结果,例如将正文写入文件,您可能会这样做像responseF.map(response => write(response.body)这样的东西。但是,如果write也是返回未来的异步方法,则此map调用将返回类似Future[Future[Result]]的类型。

在以下代码中:

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

val numF = Future{ 3 }

val stringF = numF.map(n => Future(n.toString))

val flatStringF = numF.flatMap(n => Future(n.toString))

stringF的类型为Future[Future[String]],而flatStringF的类型为Future[String]。大多数人会同意,第二个更有用。因此,平面地图可用于组合多个未来。

当您对期货使用for理解时,flatMapmap一起使用。

import scala.concurrent.{Await, Future}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._

val threeF = Future(3)
val fourF = Future(4)
val fiveF = Future(5)

val resultF = for{
  three <- threeF
  four <- fourF
  five <- fiveF
}yield{
  three * four * five
}

Await.result(resultF, 3 seconds)

此代码将产生60。

在引擎盖下,scala将其翻译为

val resultF = threeF.flatMap(three => fourF.flatMap(four => fiveF.map(five => three * four * five)))

答案 2 :(得分:1)

{{1}}

这是一个简单的例子,其中flatMap如何为Option工作,这有助于更好地理解,实际上是在编写它不会再添加包装器。这就是我们需要的。

答案 3 :(得分:0)

转换函数失败的可能性未来存在flatMap的另一个原因。

假设您有一个f: Future[T]。以及转换func: T => B。但是此功能可能由于某些原因而失败。因此我们想指出呼叫者失败了。

仅使用Future.map并不清楚如何实现这一目标。但是使用flatMap可以。因为使用flatMap会将Future作为返回值,然后您可以轻松地执行Future.failed(e)将错误冒泡给调用者。或者,如果成功,则可以使用Future.success(r)返回结果。

aka。将func变成func: T => Future[B]

当您将操作与Future链接在一起并且操作可能在中间失败时,这非常有用。