Was ist der Unterschied zwischen einer Zukunft und einem Versprechen?


274

Was ist der Unterschied zwischen Futureund Promise?
Beide fungieren als Platzhalter für zukünftige Ergebnisse, aber wo liegt der Hauptunterschied?


99
Sie können ein machen Promiseund es liegt an Ihnen, es zu behalten. Wenn jemand anderes Ihnen ein Versprechen macht, müssen Sie abwarten, ob er es imFuture
Kevin Wright


30
Einer der am wenigsten hilfreichen Wikipedia-Artikel, die ich je gelesen habe
Fulluphigh

Antworten:


145

Nach dieser Diskussion , Promisewird schließlich wurde genannt CompletableFuturefür die Aufnahme in Java 8 und seine javadoc erklärt:

Eine Zukunft, die explizit abgeschlossen werden kann (Festlegen ihres Werts und Status) und als CompletionStage verwendet werden kann und abhängige Funktionen und Aktionen unterstützt, die nach ihrem Abschluss ausgelöst werden.

Ein Beispiel ist auch in der Liste angegeben:

f.then((s -> aStringFunction(s)).thenAsync(s -> ...);

Beachten Sie, dass sich die endgültige API geringfügig unterscheidet, jedoch eine ähnliche asynchrone Ausführung ermöglicht:

CompletableFuture<String> f = ...;
f.thenApply(this::modifyString).thenAccept(System.out::println);

78
Es ist nicht deine Schuld, Assylias, aber dieser Javadoc-Extrakt muss von einem anständigen Tech-Autor ernsthaft überarbeitet werden. Bei meinem fünften Durchlesen kann ich anfangen zu verstehen, was es zu sagen versucht ... und ich komme dazu mit einem Verständnis für die Zukunft und die Versprechen, die bereits vorhanden sind!
Rote Beete-Rote Beete

2
@ Beetroot-Beetroot es scheint, dass das inzwischen passiert ist.
Hermann

1
@herman Danke - Ich habe den Link aktualisiert, um auf die endgültige Version des Javadoc zu verweisen.
Assylias

7
@ Rote Beete-Rote Beete Sie sollten das Dokument für die Ausnahmemethode überprüfen. Es wäre ein wunderbares Gedicht, aber es ist ein außergewöhnlicher Fehler in der lesbaren Dokumentation.
Fulluphigh

4
Für alle, die sich fragen, bezieht sich @Fulluphigh darauf . Sieht aus wie es in Java 8 entfernt / überarbeitet wurde.
Cedric Reichenbach

147

(Ich bin mit den Antworten bisher nicht ganz zufrieden, also hier ist mein Versuch ...)

Ich denke, dass Kevin Wrights Kommentar ( "Sie können ein Versprechen machen und es liegt an Ihnen, es zu halten. Wenn jemand anderes Ihnen ein Versprechen macht, müssen Sie abwarten, ob er es in Zukunft einhält" ) fasst es ziemlich gut zusammen, aber einige Erklärung kann nützlich sein.

Futures und Versprechen sind ziemlich ähnliche Konzepte. Der Unterschied besteht darin, dass eine Zukunft ein schreibgeschützter Container für ein Ergebnis ist, das noch nicht existiert, während ein Versprechen geschrieben werden kann (normalerweise nur einmal). Die Java 8 CompletableFuture und die Guava SettableFuture können als Versprechen angesehen werden, da ihr Wert festgelegt ("abgeschlossen") werden kann, sie aber auch die Future-Schnittstelle implementieren, sodass für den Client kein Unterschied besteht.

Das Ergebnis der Zukunft wird von "jemand anderem" festgelegt - durch das Ergebnis einer asynchronen Berechnung. Beachten Sie, dass FutureTask - eine klassische Zukunft - mit einem Callable oder Runnable initialisiert werden muss , es keinen Konstruktor ohne Argumente gibt und sowohl Future als auch FutureTask von außen schreibgeschützt sind (die festgelegten Methoden von FutureTask sind geschützt). Der Wert wird von innen auf das Ergebnis der Berechnung gesetzt.

Andererseits kann das Ergebnis eines Versprechens jederzeit von "Ihnen" (oder tatsächlich von irgendjemandem) festgelegt werden, da es über eine öffentliche Setter-Methode verfügt. Sowohl CompletableFuture als auch SettableFuture können ohne Aufgabe erstellt und ihr Wert kann jederzeit festgelegt werden. Sie senden ein Versprechen an den Kundencode und erfüllen es später nach Ihren Wünschen.

Beachten Sie, dass CompletableFuture kein "reines" Versprechen ist, sondern mit einer Aufgabe wie FutureTask initialisiert werden kann. Die nützlichste Funktion ist die unabhängige Verkettung von Verarbeitungsschritten.

Beachten Sie auch, dass ein Versprechen kein Subtyp der Zukunft sein muss und nicht dasselbe Objekt sein muss. In Scala wird ein Future-Objekt durch eine asynchrone Berechnung oder durch ein anderes Promise-Objekt erstellt. In C ++ ist die Situation ähnlich: Das Versprechen-Objekt wird vom Produzenten und das zukünftige Objekt vom Verbraucher verwendet. Der Vorteil dieser Trennung ist, dass der Kunde den Wert der Zukunft nicht festlegen kann.

Sowohl Spring als auch EJB 3.1 haben eine AsyncResult-Klasse, die den Scala / C ++ - Versprechungen ähnelt. AsyncResult implementiert Future, aber dies ist nicht die wirkliche Zukunft: Asynchrone Methoden in Spring / EJB geben durch Hintergrundmagie ein anderes schreibgeschütztes Future-Objekt zurück, und diese zweite "echte" Zukunft kann vom Client verwendet werden, um auf das Ergebnis zuzugreifen.


116

Mir ist bewusst, dass es bereits eine akzeptierte Antwort gibt, möchte aber trotzdem meine zwei Cent hinzufügen:

TLDR: Future und Promise sind die beiden Seiten einer asynchronen Operation: Consumer / Caller vs. Producer / Implementor .

Als Aufrufer einer asynchronen API-Methode erhalten Sie Futureein Handle für das Ergebnis der Berechnung. Sie können es beispielsweise aufrufen, get()um zu warten, bis die Berechnung abgeschlossen ist, und das Ergebnis abzurufen.

Stellen Sie sich nun vor, wie diese API-Methode tatsächlich implementiert ist: Der Implementierer muss Futuresofort eine zurückgeben. Sie sind dafür verantwortlich, diese Zukunft abzuschließen, sobald die Berechnung abgeschlossen ist (was sie wissen werden, weil es die Versandlogik implementiert ;-)). Sie werden ein Promise/ verwenden CompletableFuture, um genau das zu tun: Konstruieren und geben Sie das CompletableFuturesofort zurück und rufen Sie auf, complete(T result)sobald die Berechnung abgeschlossen ist.


1
Bedeutet dies, dass ein Versprechen immer eine Unterklasse der Zukunft ist und dass die Schreibbarkeit der Zukunft nur durch den Typ verdeckt wird?
Devios1

Ich denke nicht, dass es impliziert ist . In Bezug auf die Implementierung ist dies jedoch häufig der Fall (z. B. in Java, Scala).
Rahel Lüthy

74

Ich werde ein Beispiel geben, was Versprechen ist und wie sein Wert jederzeit eingestellt werden kann, im Gegensatz zu Future, wobei dieser Wert nur lesbar ist.

Angenommen, Sie haben eine Mutter und bitten sie um Geld.

// Now , you trick your mom into creating you a promise of eventual
// donation, she gives you that promise object, but she is not really
// in rush to fulfill it yet:
Supplier<Integer> momsPurse = ()-> {

        try {
            Thread.sleep(1000);//mom is busy
        } catch (InterruptedException e) {
            ;
        }

        return 100;

    };


ExecutorService ex = Executors.newFixedThreadPool(10);

CompletableFuture<Integer> promise =  
CompletableFuture.supplyAsync(momsPurse, ex);

// You are happy, you run to thank you your mom:
promise.thenAccept(u->System.out.println("Thank you mom for $" + u ));

// But your father interferes and generally aborts mom's plans and 
// completes the promise (sets its value!) with far lesser contribution,
// as fathers do, very resolutely, while mom is slowly opening her purse 
// (remember the Thread.sleep(...)) :
promise.complete(10); 

Die Ausgabe davon ist:

Thank you mom for $10

Mamas Versprechen wurde erstellt, wartete aber auf ein "Abschluss" -Ereignis.

CompletableFuture<Integer> promise...

Sie haben ein solches Ereignis ins Leben gerufen, ihr Versprechen angenommen und Ihre Pläne angekündigt, Ihrer Mutter zu danken:

promise.thenAccept...

In diesem Moment fing Mama an, ihre Handtasche zu öffnen ... aber sehr langsam ...

und Vater mischte sich viel schneller ein und erfüllte das Versprechen anstelle deiner Mutter:

promise.complete(10);

Haben Sie einen Testamentsvollstrecker bemerkt, den ich explizit geschrieben habe?

Interessanterweise wird ihr Versprechen nur dann erfüllt, wenn Sie stattdessen einen impliziten Standard-Executor (commonPool) verwenden und der Vater nicht zu Hause ist, sondern nur die Mutter mit ihrem "langsamen Geldbeutel", wenn das Programm länger lebt, als die Mutter Geld von der benötigt Geldbörse.

Der Standard-Executor verhält sich wie ein "Daemon" und wartet nicht darauf, dass alle Versprechen erfüllt werden. Ich habe keine gute Beschreibung dieser Tatsache gefunden ...


8
Es macht so viel Spaß, diesen zu lesen! Ich glaube nicht, dass ich die Zukunft vergessen und mehr versprechen könnte.
user1532146

2
Dies muss als Antwort akzeptiert werden. Es ist wie beim Lesen einer Geschichte. Danke @Vladimir
Phillen

Danke @Vladimir
intvprep

9

Ich bin mir nicht sicher, ob dies eine Antwort sein kann, aber wie ich sehe, was andere für jemanden gesagt haben, sieht es möglicherweise so aus, als ob Sie für beide Konzepte zwei separate Abstraktionen benötigen, sodass eine von ihnen ( Future) nur eine schreibgeschützte Ansicht der anderen ist ( Promise) ... aber eigentlich wird das nicht benötigt.

Schauen Sie sich zum Beispiel an, wie Versprechen in Javascript definiert sind:

https://promisesaplus.com/

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

Der Fokus liegt auf der Zusammensetzbarkeit mit der thenMethode wie:

asyncOp1()
.then(function(op1Result){
  // do something
  return asyncOp2();
})
.then(function(op2Result){
  // do something more
  return asyncOp3();
})
.then(function(op3Result){
  // do something even more
  return syncOp4(op3Result);
})
...
.then(function(result){
  console.log(result);
})
.catch(function(error){
  console.log(error);
})

Dadurch sieht die asynchrone Berechnung synchron aus:

try {
  op1Result = syncOp1();
  // do something
  op1Result = syncOp2();
  // do something more
  op3Result = syncOp3();
  // do something even more
  syncOp4(op3Result);
  ...
  console.log(result);
} catch(error) {
  console.log(error);
}

Das ist ziemlich cool. (Nicht so cool wie async-await, aber async-await entfernt nur die Boilerplate .... then (function (result) {.... from it).

Und tatsächlich ist ihre Abstraktion als Versprechenskonstruktor ziemlich gut

new Promise( function(resolve, reject) { /* do it */ } );

Mit dieser Option können Sie zwei Rückrufe bereitstellen, mit denen Sie den Vorgang entweder Promiseerfolgreich oder mit einem Fehler abschließen können . Damit nur der Code, der das Promiseerstellt, es vervollständigen kann und der Code, der ein bereits erstelltes PromiseObjekt empfängt, die schreibgeschützte Ansicht hat.

Mit der Vererbung kann das Obige erreicht werden, wenn Auflösung und Zurückweisung geschützte Methoden sind.


4
+1. Dies ist die richtige Antwort auf diese Frage. CompletableFutureMöglicherweise hat es eine gewisse Ähnlichkeit mit a, Promiseaber es ist immer noch keinePromise , da die Art und Weise, wie es konsumiert werden soll, unterschiedlich ist: PromiseDas Ergebnis von a wird durch Aufrufen konsumiert then(function), und die Funktion wird unmittelbar nach dem Aufruf des Produzenten im Kontext des Produzenten ausgeführt resolve. FutureDas Ergebnis von A wird durch einen Aufruf verbraucht, getwodurch der Consumer-Thread wartet, bis der Producer-Thread den Wert generiert hat, und ihn dann im Consumer verarbeitet. Futureist von Natur aus multithreaded, aber ...
Periata Breatta

5
... ist es durchaus möglich, a Promisemit nur einem Thread zu verwenden (und tatsächlich ist dies die genaue Umgebung, für die sie ursprünglich entwickelt wurden: Javascript-Anwendungen haben im Allgemeinen nur einen einzigen Thread, sodass Sie sie dort nicht implementieren Futurekönnen). Promiseist daher viel leichter und effizienter als Future, Futurekann aber in Situationen hilfreich sein, die komplexer sind und die Zusammenarbeit zwischen Threads erfordern, die mit Promises nicht einfach angeordnet werden können . Zusammenfassend: Promiseist ein Push-Modell, während Futurees ein Pull-Modell ist (vgl. Iterable vs Observable)
Periata Breatta

@PeriataBreatta Selbst in einer Umgebung mit einem Thread muss etwas vorhanden sein, das das Versprechen erfüllt (das normalerweise als ein anderer Thread ausgeführt wird, z XMLHttpRequest. B. ein ). Ich glaube dem Effizienzanspruch nicht, haben Sie zufällig Zahlen? +++ Das heißt, eine sehr schöne Erklärung.
Maaartinus

1
@maaartinus - ja, etwas muss das Versprechen erfüllen, aber es kann (und wird in vielen Fällen sogar) mithilfe einer Schleife der obersten Ebene durchgeführt werden, die nach Änderungen des externen Zustands fragt und alle Versprechen löst, die sich auf abgeschlossene Aktionen beziehen. In Bezug auf die Effizienz habe ich keine konkreten Zahlen für Versprechen speziell, aber beachten Sie, dass das Aufrufen geteines ungelösten FutureProblems zwangsläufig zwei Thread-Kontextwechsel erfordert, für die vor mindestens einigen Jahren wahrscheinlich etwa 50 US erforderlich waren .
Periata Breatta

@PeriataBreatta Eigentlich sollte Ihr Kommentar die akzeptierte Lösung sein. Ich suchte nach einer Erklärung (Pull / Push, Single / Multi-Thread) wie Ihrer.
Thomas Jacob

5

Bei Client-Code dient Promise zum Beobachten oder Anhängen eines Rückrufs, wenn ein Ergebnis verfügbar ist, während Future auf das Ergebnis warten und dann fortfahren soll. Theoretisch alles, was mit Futures zu tun ist, was mit Versprechungen getan werden kann, aber aufgrund des Stilunterschieds erleichtert die resultierende API für Versprechungen in verschiedenen Sprachen die Verkettung.


2

Keine festgelegte Methode in der Future-Schnittstelle, nur get-Methode, daher schreibgeschützt. Über CompletableFuture kann dieser Artikel hilfreich sein. vervollständigbare Zukunft

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.