[英]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
.我已经阅读了关于map
和flatMap
的文档,我知道flatMap
用于接受Future
参数并返回另一个Future
。 What I don't fully understand is why I would want to do this.我不完全理解的是我为什么要这样做。 Take this example:拿这个例子:
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
),我发现在这两种情况下,下载、 process
和render
发生在同一个线程中(使用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
来分离downloadFile
和processFile
并确保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]
. stringF
是Future[Future[String]]
类型,而flatStringF
是Future[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 aFuture
even if it was not mapped fromdownloadFile
?确保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.