我已经阅读了关于map
和flatMap
的文档,我理解flatMap
用于接受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
映射?
确保
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)
)
)
或者使用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))
}
如果你有一个未来,让我们说,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)))
def flatMap[B](f: A => Option[B]): Option[B] =
this match {
case None => None
case Some(a) => f(a)
}
这是一个简单的例子,其中flatMap如何为Option工作,这有助于更好地理解,实际上是在编写它不会再添加包装器。这就是我们需要的。