期货 - 地图与平面地图

24 scala future

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

  1. 用户点击我的网络服务要求"做东西"
  2. 我下载了一个文件(很慢)
  3. 我处理文件(CPU密集型)
  4. 渲染结果

我知道我想使用未来下载文件,但我有两个选项重新处理它:

val downloadFuture = Future { downloadFile }
val processFuture = downloadFuture map { processFile }
processFuture onSuccess { case r => renderResult(r) }
Run Code Online (Sandbox Code Playgroud)

要么

val downloadFuture = Future { // download the file }
val processFuture = downloadFuture flatMap { Future { processFile } }
processFuture onSuccess { case r => renderResult(r) }
Run Code Online (Sandbox Code Playgroud)

通过添加调试语句(Thread.currentThread().getId),我看到在两种情况下都下载,processrender发生在同一个线程中(使用ExecutionContext.Implicits.global).

我会flatMap简单地使用它来解耦downloadFileprocessFile确保即使它没有映射也processFile总是运行吗?FuturedownloadFile

mat*_*its 26

如果你有一个未来,让我们说,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))
Run Code Online (Sandbox Code Playgroud)

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

当你for对Futures 使用理解时,引擎盖flatMap正与它一起使用map.

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)
Run Code Online (Sandbox Code Playgroud)

此代码将产生60.

在引擎盖下,scala将其转换为

val resultF = threeF.flatMap(three => fourF.flatMap(four => fiveF.map(five => three * four * five)))
Run Code Online (Sandbox Code Playgroud)


Pet*_*ens 24

即使它没有映射,也要确保processFile始终以a运行?FuturedownloadFile

对,那是正确的.

但是大多数情况下你不会Future { ... }直接使用,你会使用返回a的函数(来自其他库或你自己的函数)Future.

想象一下以下功能:

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

你可以flatMap用来组合它们:

val futResult: Future[ProcessResult] =
  getFileNameFromDB(1).flatMap( name =>
    downloadFile(name).flatMap( file =>
       processFile(file)
    )
  )
Run Code Online (Sandbox Code Playgroud)

或者使用for comprehension:

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

大多数时候你不会打电话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))
}
Run Code Online (Sandbox Code Playgroud)