2015-07-26 4 views
10

Ich habe die Dokumentation zu map und flatMap gelesen und ich verstehe, dass flatMap für eine Operation verwendet wird, die einen Future Parameter akzeptiert und gibt einen anderen Future. Was ich nicht vollständig verstehe ist, warum ich das machen möchte. Nehmen Sie dieses Beispiel:Futures - Karte vs flatmap

  1. Benutzer meine WebService trifft fragen, "do stuff"
  2. ich eine Datei herunterladen (die langsam ist)
  3. ich die Datei bearbeiten
  4. Render (die CPU-intensiv ist) die
  5. Ergebnis

ich verstehe, dass ich eine Zukunft nutzen möchte, um die Datei herunterzuladen, aber ich habe zwei Möglichkeiten wieder verarbeitet es:

val downloadFuture = Future { downloadFile } 
val processFuture = downloadFuture map { processFile } 
processFuture onSuccess { case r => renderResult(r) } 

oder

val downloadFuture = Future { // download the file } 
val processFuture = downloadFuture flatMap { Future { processFile } } 
processFuture onSuccess { case r => renderResult(r) } 

von Debug-Anweisungen hinzugefügt (Thread.currentThread().getId) Ich sehe, dass in beiden Fällen herunterladen, process und render im selben Thread auftreten (mit ExecutionContext.Implicits.global).

Würde ich flatMap einfach zu entkoppeln downloadFile und processFile und sicherzustellen, dass processFile läuft immer in einem Future auch wenn es nicht von downloadFile abgebildet wurde?

Antwort

14

sicherstellen, dass processFile läuft immer in einem Future auch wenn es nicht von downloadFile abgebildet wurde?

Ja das ist richtig.

Aber die meiste Zeit würden Sie nicht Future { ... } direkt verwenden, würden Sie Funktionen (aus anderen Bibliotheken oder Ihre eigenen) verwenden, die eine Future zurückgeben.

folgende Funktionen Stellen Sie sich vor:

def getFileNameFromDB{id: Int) : Future[String] = ??? 
def downloadFile(fileName: String) : Future[java.io.File] = ??? 
def processFile(file: java.io.File) : Future[ProcessResult] = ??? 

Sie flatMap sie kombinieren verwenden:

val futResult: Future[ProcessResult] = 
    getFileNameFromDB(1).flatMap(name => 
    downloadFile(name).flatMap(file => 
     processFile(file) 
    ) 
) 

Oder ein für das Verständnis mit:

val futResult: Future[ProcessResult] = 
    for { 
    name <- getFileNameFromDB(1) 
    file <- downloadFile(name) 
    result <- processFile(file) 
    } yield result 

Sie die meiste Zeit würde nicht anrufen onSuccess (oder onComplete). Mit einer dieser Funktionen registrieren Sie eine Callback-Funktion, die ausgeführt wird, wenn die Future beendet wird.

Wenn Sie in unserem Beispiel das Ergebnis der Dateiverarbeitung rendern möchten, geben Sie so etwas wie Future[Result] statt futResult.onSuccess(renderResult) zurück. Im letzten Fall wäre Ihr Rückgabetyp Unit, Sie können also nicht wirklich etwas zurückgeben.

Im Play-Framework könnte dies wie folgt aussehen:

def giveMeAFile(id: Int) = Action.async { 
    for { 
    name <- getFileNameFromDB(1) 
    file <- downloadFile(name) 
    processed <- processFile(file) 
    } yield Ok(processed.byteArray).as(processed.mimeType)) 
} 
+0

Danke für die Bestätigung dieser Peter –

12

Wenn Sie eine Zukunft haben, lassen Sie uns sagen, Future[HttpResponse], und Sie wollen bestimmen, was mit diesem Ergebnis zu tun, wenn sie bereit ist, wie die schreiben Körper in eine Datei, können Sie etwas wie responseF.map(response => write(response.body) tun. Wenn write auch eine asynchrone Methode ist, die eine Zukunft zurückgibt, wird dieser map Aufruf einen Typ wie Future[Future[Result]] zurückgeben.

Im folgenden 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 ist vom Typ Future[Future[String]] während flatStringF vom Typ Future[String]. Die meisten würden zustimmen, die zweite ist nützlicher. Flat Map ist daher nützlich, um mehrere Futures zusammen zu bilden.

Wenn Sie for Comprehensions mit Futures verwenden, wird unter der Haube flatMap zusammen mit map verwendet.

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) 

Dieser Code 60.

Unter der Haube führen wird, übersetzt scala diese

zu
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) 
    } 

Dies ist ein einfaches Beispiel, in dem, wie die flatMap für Option funktioniert, kann dies helfen, besser zu verstehen, es ist tatsächlich Komponieren, es ist nicht ein Wrapper wieder hinzufügen. Das ist, was wir brauchen.