繁体   English   中英

期货 - 地图与平面地图

[英]Futures - map vs flatmap

我已经阅读了关于mapflatMap的文档,我知道flatMap用于接受Future参数并返回另一个Future 我不完全理解的是我为什么要这样做。 拿这个例子:

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

我知道我想使用 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().getId ),我发现在这两种情况下,下载、 processrender发生在同一个线程中(使用ExecutionContext.Implicits.global )。

我会使用flatMap来分离downloadFileprocessFile并确保processFile始终在Future运行,即使它不是从downloadFile映射的吗?

如果你有一个未来,比方说, 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))

stringFFuture[Future[String]]类型,而flatStringFFuture[String]类型。 大多数人会同意,第二个更有用。 因此,平面地图对于将多个期货组合在一起很有用。

当您使用for与期货内涵,引擎盖下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)

此代码将产生 60。

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

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

确保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)
    )
  )

或使用 a 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))
}
def flatMap[B](f: A => Option[B]): Option[B] = 
  this match {
    case None => None
    case Some(a) => f(a)
  }

这是一个简单的例子,说明了 flatMap 如何为 Option 工作,这可以帮助更好地理解,它实际上是在组合它而不是再次添加包装器。这就是我们需要的。

转换函数的失败可能性是 Future 上存在flatMap另一个原因。

假设你有一个f: Future[T] 和一个转换func: T => B 但是,由于某种原因,此功能可能会失败。 所以我们想表明它失败的调用者。

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

又名。 把 func 变成func: T => Future[B]

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM