期货 - 地图与平面图
Futures - map vs flatmap
我已经阅读了有关 map
和 flatMap
的文档,我知道 flatMap
用于接受 Future
参数和 returns 另一个 Future
。我不完全理解的是为什么我要这样做。举个例子:
- 用户点击我的网络服务请求 "do stuff"
- 我下载一个文件(很慢)
- 我处理文件(CPU 密集)
- 渲染结果
我知道我想使用 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
?
ensure that processFile
always runs in a Future
even if it was not mapped from downloadFile
?
是的,没错。
但是大多数时候你不会直接使用 Future { ... }
,你会使用函数(来自其他库或你自己的)return a 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
完成时执行。
如果在我们的示例中您想要呈现文件处理的结果,您将 return 类似于 Future[Result]
而不是调用 futResult.onSuccess(renderResult)
。在最后一种情况下,您的 return 类型将是 Unit
,因此您不能真正 return 某些东西。
在 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
也是一个异步方法,return 是一个未来,这个 map
调用将 return 像 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]
类型。大多数人会同意,第二个更有用。因此,Flat Map 对于将多个 future 组合在一起很有用。
当您对 Futures 使用 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 工作,这有助于更好地理解,它实际上是在组合它而不是添加我们需要的包装器again.That。
转换函数的失败可能性是 Future 存在 flatMap
的另一个原因。
说你有一个 f: Future[T]
。和一个转换 func: T => B
。然而,由于某种原因,这个功能可能会失败。所以我们想指示调用者它失败了。
仅 Future.map
如何实现这一点并不明显。但是 flatMap
你可以。因为使用 flatMap
它将 Future 视为 return,然后您可以轻松地执行 Future.failed(e) 以将错误冒泡给调用者。或者,如果成功,您可以使用 Future.success(r) 来 return 结果。
又名。把 func 变成 func: T => Future[B]
当您将操作与 Future 链接在一起并且操作可能在中间失败时,这非常有用。
我已经阅读了有关 map
和 flatMap
的文档,我知道 flatMap
用于接受 Future
参数和 returns 另一个 Future
。我不完全理解的是为什么我要这样做。举个例子:
- 用户点击我的网络服务请求 "do stuff"
- 我下载一个文件(很慢)
- 我处理文件(CPU 密集)
- 渲染结果
我知道我想使用 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
?
ensure that
processFile
always runs in aFuture
even if it was not mapped fromdownloadFile
?
是的,没错。
但是大多数时候你不会直接使用 Future { ... }
,你会使用函数(来自其他库或你自己的)return a 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
完成时执行。
如果在我们的示例中您想要呈现文件处理的结果,您将 return 类似于 Future[Result]
而不是调用 futResult.onSuccess(renderResult)
。在最后一种情况下,您的 return 类型将是 Unit
,因此您不能真正 return 某些东西。
在 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
也是一个异步方法,return 是一个未来,这个 map
调用将 return 像 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]
类型。大多数人会同意,第二个更有用。因此,Flat Map 对于将多个 future 组合在一起很有用。
当您对 Futures 使用 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 工作,这有助于更好地理解,它实际上是在组合它而不是添加我们需要的包装器again.That。
转换函数的失败可能性是 Future 存在 flatMap
的另一个原因。
说你有一个 f: Future[T]
。和一个转换 func: T => B
。然而,由于某种原因,这个功能可能会失败。所以我们想指示调用者它失败了。
仅 Future.map
如何实现这一点并不明显。但是 flatMap
你可以。因为使用 flatMap
它将 Future 视为 return,然后您可以轻松地执行 Future.failed(e) 以将错误冒泡给调用者。或者,如果成功,您可以使用 Future.success(r) 来 return 结果。
又名。把 func 变成 func: T => Future[B]
当您将操作与 Future 链接在一起并且操作可能在中间失败时,这非常有用。