[英]Futures - map vs flatmap
我已经阅读了关于map
和flatMap
的文档,我知道flatMap
用于接受Future
参数并返回另一个Future
。 我不完全理解的是我为什么要这样做。 拿这个例子:
我知道我想使用 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
),我发现在这两种情况下,下载、 process
和render
发生在同一个线程中(使用ExecutionContext.Implicits.global
)。
我会使用flatMap
来分离downloadFile
和processFile
并确保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))
stringF
是Future[Future[String]]
类型,而flatStringF
是Future[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.