2015-07-26 4 views
10

Я читал документы о map и flatMap, и я понимаю, что flatMap используется для операции, которая принимает параметр Future и возвращает другой Future. Я не совсем понимаю, почему я хотел бы это сделать. Возьмем такой пример:Futures - карта против flatmap

  1. Пользователь попадает мой веб-сервиса с просьбой «делать вещи»
  2. загрузить файл (который медленно)
  3. Я обработать файл (который является ресурсоемкие)
  4. Отрендерьте результат

Я понимаю, что я хотел бы использовать будущее, чтобы загрузить файл, но у меня есть два варианта повторного его обработки:

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?

ответ

14

убедитесь, что 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) 
    ) 
) 

Или используя для понимания:

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)) 
} 
+0

Спасибо за подтверждение этого peter –

12

Если у вас есть будущее, скажем, 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))) 
0
def flatMap[B](f: A => Option[B]): Option[B] = 
    this match { 
    case None => None 
    case Some(a) => f(a) 
    } 

Это простой пример того, как работает flatMap для варианта, это может помогите понять лучше, Это на самом деле составление, это не добавление обертки снова. Это то, что нам нужно.

Смежные вопросы