简体   繁体   English

期货 - 地图与平面地图

[英]Futures - map vs flatmap

I've read the docs about map and flatMap and I understand that flatMap is used for an operation that accepts a Future parameter and returns another Future .我已经阅读了关于mapflatMap的文档,我知道flatMap用于接受Future参数并返回另一个Future What I don't fully understand is why I would want to do this.我不完全理解的是我为什么要这样做。 Take this example:拿这个例子:

  1. User hits my webservice asking to "do stuff"用户点击我的网络服务要求“做事”
  2. I download a file (which is slow)我下载了一个文件(很慢)
  3. I process the file (which is CPU intensive)我处理文件(这是 CPU 密集型的)
  4. Render the result渲染结果

I understand that I would want to use a future to download the file but I have have two options re processing it:我知道我想使用 future 来下载文件,但我有两个选项可以重新处理它:

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

or或者

val downloadFuture = Future {/* download the file */}
val processFuture = downloadFuture flatMap { Future {/* processFile */} }
processFuture onSuccess { case r => renderResult(r) }

By adding debug statements ( Thread.currentThread().getId ) I see that in both cases download, process and render occur in the same thread (using ExecutionContext.Implicits.global ).通过添加调试语句( Thread.currentThread().getId ),我发现在这两种情况下,下载、 processrender发生在同一个线程中(使用ExecutionContext.Implicits.global )。

Would I use flatMap simply to decouple downloadFile and processFile and ensure that processFile always runs in a Future even if it was not mapped from downloadFile ?我会使用flatMap来分离downloadFileprocessFile并确保processFile始终在Future运行,即使它不是从downloadFile映射的吗?

If you have a future, let's say, Future[HttpResponse] , and you want to specify what to do with that result when it is ready, such as write the body to a file, you may do something like responseF.map(response => write(response.body) . However if write is also an asynchronous method which returns a future, this map call will return a type like Future[Future[Result]] .如果你有一个未来,比方说, Future[HttpResponse] ,并且你想在它准备好时指定如何处理该结果,例如将主体写入文件,你可以执行类似responseF.map(response => write(response.body) . 然而,如果write也是一个返回未来的异步方法,这个map调用将返回一个类似Future[Future[Result]]

In the following code:在以下代码中:

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 is of type Future[Future[String]] while flatStringF is of type Future[String] . stringFFuture[Future[String]]类型,而flatStringFFuture[String]类型。 Most would agree, the second is more useful.大多数人会同意,第二个更有用。 Flat Map is therefore useful for composing multiple futures together.因此,平面地图对于将多个期货组合在一起很有用。

When you use for comprehensions with Futures, under the hood flatMap is being used together with map .当您使用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)

This code will yield 60.此代码将产生 60。

Under the hood, scala translates this to在引擎盖下,scala 将其转换为

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

ensure that processFile always runs in a Future even if it was not mapped from downloadFile ?确保processFile始终在Future运行,即使它不是从downloadFile映射的?

Yes that is correct.对,那是正确的。

However most of the time you wouldn't use Future { ... } directly, you would use functions (from other libraries or your own) which return a Future .然而,大多数时候你不会直接使用Future { ... } ,你会使用返回Future函数(来自其他库或你自己的)。

Imagine the following functions :想象一下以下功能:

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

You could use flatMap to combine them :您可以使用flatMap将它们组合起来:

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

Or using a for comprehension :或使用 a for comprehension :

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

Most of the time you would not call onSuccess (or onComplete ).大多数时候你不会调用onSuccess (或onComplete )。 By using one of these functions you register a callback function which will be executed when the Future finishes.通过使用这些函数之一,您可以注册一个回调函数,该函数将在Future完成时执行。

If in our example you would like to render the result of the file processing, you would return something like Future[Result] instead of calling futResult.onSuccess(renderResult) .如果在我们的示例中您想呈现文件处理的结果,您将返回类似Future[Result]而不是调用futResult.onSuccess(renderResult) In the last case your return type would be Unit , so you can not really return something.在最后一种情况下,您的返回类型将是Unit ,因此您无法真正返回某些内容。

In Play Framework this could look like :在 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)
  }

This is a simple example where how the flatMap works for Option, this can help to understand better, It is actually composing it is not adding a wrapper again.That's what we need.这是一个简单的例子,说明了 flatMap 如何为 Option 工作,这可以帮助更好地理解,它实际上是在组合它而不是再次添加包装器。这就是我们需要的。

Failure possibility from transformation function another reason why there exists flatMap on Future.转换函数的失败可能性是 Future 上存在flatMap另一个原因。

say you have a f: Future[T] .假设你有一个f: Future[T] and a transformation func: T => B .和一个转换func: T => B however this func could fail due to some reason.但是,由于某种原因,此功能可能会失败。 so we want to indicate the caller it has failed.所以我们想表明它失败的调用者。

with just Future.map it's not obvious how to achieve this.仅使用Future.map ,如何实现这一点并不明显。 but with flatMap you can.但是使用flatMap可以。 because with flatMap it takes Future as return, and you can then easily do Future.failed(e) to bubble up the error to the caller.因为使用flatMap它将 Future 作为回报,然后您可以轻松地执行 Future.failed(e) 将错误冒泡给调用者。 or if it succeeded, you can then use Future.success(r) to return the result.或者,如果成功,则可以使用 Future.success(r) 返回结果。

aka.又名。 turn func into func: T => Future[B]把 func 变成func: T => Future[B]

this is very useful when you are chaining operations with Future and operations could fail in the middle.当您使用 Future 链接操作并且操作可能在中间失败时,这非常有用。

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

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