Quelle est la différence entre un avenir et une promesse?

275

Quelle est la différence entre Futureet Promise?
Ils agissent tous deux comme un espace réservé pour les résultats futurs, mais où est la principale différence?

user1170330
la source
100
Vous pouvez en faire un Promiseet c'est à vous de le garder. Quand quelqu'un d'autre vous fait une promesse, vous devez attendre de voir s'il l'honore dans leFuture
Kevin Wright
1
wikipedia Futures et promesses
wener
30
L'un des articles Wikipedia les moins utiles que j'aie jamais lu
Fulluphigh

Réponses:

146

Selon cette discussion , Promisea finalement été appelé CompletableFutureà être inclus dans Java 8, et son javadoc explique:

Un Futur qui peut être explicitement achevé (en définissant sa valeur et son statut), et qui peut être utilisé comme CompletionStage, prenant en charge les fonctions et actions dépendantes qui se déclenchent lors de son achèvement.

Un exemple est également donné sur la liste:

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

Notez que l'API finale est légèrement différente mais permet une exécution asynchrone similaire:

CompletableFuture<String> f = ...;
f.thenApply(this::modifyString).thenAccept(System.out::println);
assylias
la source
78
Ce n'est pas de votre faute Assylias, mais cet extrait de javadoc a besoin d'un travail sérieux par un auteur technique décent. Sur ma cinquième lecture, je peux juste commencer à apprécier ce qu'il essaie de dire ... et j'y arrive avec une compréhension des futurs et des promesses déjà en place!
Beetroot-Beetroot
2
@ Beetroot-Beetroot, il semble que ce soit déjà arrivé.
herman
1
@herman Merci - J'ai mis à jour le lien pour pointer vers la version finale du javadoc.
assylias
7
@ Beetroot-Beetroot Vous devriez consulter le doc pour la méthode Exceptionnellement. Ce serait un merveilleux poème, mais c'est un échec exceptionnel de la documentation lisible.
Fulluphigh
4
Pour tous ceux qui se demandent, @Fulluphigh fait référence à cela . On dirait qu'il a été retiré / révisé en Java 8.
Cedric Reichenbach
148

(Je ne suis pas complètement satisfait des réponses jusqu'à présent, alors voici ma tentative ...)

Je pense que le commentaire de Kevin Wright ( "Vous pouvez faire une promesse et c'est à vous de la tenir. Quand quelqu'un d'autre vous fait une promesse, vous devez attendre de voir s'il l'honorera dans le futur" ) le résume assez bien, mais certains l'explication peut être utile.

Futures et promesses sont des concepts assez similaires, la différence est qu'un futur est un conteneur en lecture seule pour un résultat qui n'existe pas encore, alors qu'une promesse peut être écrite (normalement une seule fois). Le Java 8 CompletableFuture et le Guava SettableFuture peuvent être considérés comme des promesses, car leur valeur peut être définie ("terminée"), mais ils implémentent également l'interface Future, il n'y a donc pas de différence pour le client.

Le résultat du futur sera fixé par "quelqu'un d'autre" - par le résultat d'un calcul asynchrone. Notez comment FutureTask - un futur classique - doit être initialisé avec un Callable ou Runnable, il n'y a pas de constructeur sans argument, et Future et FutureTask sont en lecture seule de l'extérieur (les méthodes définies de FutureTask sont protégées). La valeur sera définie sur le résultat du calcul de l'intérieur.

D'un autre côté, le résultat d'une promesse peut être défini par "vous" (ou en fait par n'importe qui) à tout moment car il a une méthode de définition publique. CompletableFuture et SettableFuture peuvent être créés sans aucune tâche et leur valeur peut être définie à tout moment. Vous envoyez une promesse au code client et la remplissez plus tard comme vous le souhaitez.

Notez que CompletableFuture n'est pas une promesse "pure", il peut être initialisé avec une tâche tout comme FutureTask, et sa fonctionnalité la plus utile est le chaînage indépendant des étapes de traitement.

Notez également qu'une promesse n'a pas à être un sous-type de futur et qu'elle ne doit pas être le même objet. Dans Scala, un objet Future est créé par un calcul asynchrone ou par un autre objet Promise. En C ++ la situation est similaire: l'objet promesse est utilisé par le producteur et l'objet futur par le consommateur. L'avantage de cette séparation est que le client ne peut pas fixer la valeur de l'avenir.

Les deux printemps et EJB 3.1 ont une classe AsyncResult, qui est semblable aux promesses de la / Scala C. AsyncResult implémente Future mais ce n'est pas le vrai futur: les méthodes asynchrones dans Spring / EJB renvoient un objet Future différent et en lecture seule via une magie d'arrière-plan, et ce deuxième futur "réel" peut être utilisé par le client pour accéder au résultat.

lbalazscs
la source
116

Je suis conscient qu'il existe déjà une réponse acceptée, mais je voudrais néanmoins ajouter mes deux cents:

TLDR: Future et Promise sont les deux faces d'une opération asynchrone: consommateur / appelant vs producteur / réalisateur .

En tant qu'appelant d'une méthode d'API asynchrone, vous obtiendrez un en Futuretant que descripteur du résultat du calcul. Vous pouvez par exemple l'appeler get()pour attendre la fin du calcul et récupérer le résultat.

Pensez maintenant à la façon dont cette méthode API est réellement implémentée: l' implémenteur doit retourner Futureimmédiatement un . Ils sont chargés de compléter cet avenir dès que le calcul est terminé (ce qu'ils sauront car il implémente la logique de répartition ;-)). Ils utiliseront a Promise/ CompletableFuturepour faire exactement cela: Construire et retourner le CompletableFutureimmédiatement, et appeler complete(T result)une fois le calcul terminé.

Rahel Lüthy
la source
1
Cela implique-t-il qu'une promesse est toujours une sous-classe du futur et que l'écriture du futur est simplement obscurcie par le type?
devios1
Je ne pense pas que ce soit implicite . Au niveau de l'implémentation, ce sera souvent le cas (par exemple en Java, Scala).
Rahel Lüthy
74

Je vais donner un exemple de ce qu'est Promise et comment sa valeur pourrait être définie à tout moment, contrairement à Future, qui est uniquement lisible.

Supposons que vous ayez une maman et que vous lui demandiez de l'argent.

// 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); 

La sortie de cela est:

Thank you mom for $10

La promesse de maman a été créée, mais a attendu un événement "d'achèvement".

CompletableFuture<Integer> promise...

Vous avez créé un tel événement, en acceptant sa promesse et en annonçant vos plans pour remercier votre maman:

promise.thenAccept...

À ce moment, maman a commencé à ouvrir son sac à main ... mais très lentement ...

et le père est intervenu beaucoup plus rapidement et a rempli la promesse au lieu de votre maman:

promise.complete(10);

Avez-vous remarqué un exécuteur que j'ai écrit explicitement?

Fait intéressant, si vous utilisez un exécuteur implicite implicite à la place (commonPool) et que le père n'est pas à la maison, mais seulement maman avec son "sac à main lent", alors sa promesse ne se terminera que si le programme vit plus longtemps que maman a besoin d'obtenir de l'argent du bourse.

L'exécuteur par défaut agit un peu comme un "démon" et n'attend pas que toutes les promesses soient tenues. Je n'ai pas trouvé une bonne description de ce fait ...

Vladimir Nabokov
la source
8
C'est tellement amusant de lire celui-ci! Je ne pense pas que je pourrais oublier l'avenir et promettre plus.
user1532146
2
Cela doit être accepté comme réponse. C'est comme lire une histoire. Merci @Vladimir
Phillen
Merci @Vladimir
intvprep
9

Je ne sais pas si cela peut être une réponse, mais comme je vois ce que les autres ont dit pour quelqu'un, il peut sembler que vous avez besoin de deux abstractions distinctes pour ces deux concepts afin que l'un d'eux ( Future) soit juste une vue en lecture seule de l'autre ( Promise) ... mais en fait ce n'est pas nécessaire.

Par exemple, regardez comment les promesses sont définies en javascript:

https://promisesaplus.com/

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

L'accent est mis sur la composabilité en utilisant la thenméthode comme:

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

ce qui rend le calcul asynchrone comme synchrone:

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

ce qui est plutôt cool. (Pas aussi cool que async-wait mais async-wait supprime simplement le passe-partout .... puis (fonction (résultat) {.... de celui-ci).

Et en fait, leur abstraction est assez bonne en tant que constructeur de promesses

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

vous permet de fournir deux rappels qui peuvent être utilisés pour terminer Promiseavec succès ou avec une erreur. Ainsi, seul le code qui construit le Promisepeut le compléter et le code qui reçoit un Promiseobjet déjà construit a la vue en lecture seule.

Avec l'héritage, ce qui précède peut être atteint si la résolution et le rejet sont des méthodes protégées.

bodrine
la source
4
+1. Ceci est la bonne réponse à cette question. CompletableFuturepeut avoir une certaine similitude avec a Promisemais ce n'est toujours pas le casPromise , car la façon dont il est destiné à être consommé est différente: Promisele résultat de a est consommé en appelant then(function), et la fonction est exécutée dans le contexte du producteur immédiatement après l'appel du producteur resolve. Le Futurerésultat de A est consommé en appelant, getce qui oblige le thread consommateur à attendre que le thread producteur ait généré la valeur, puis la traite dans le consommateur. Futureest intrinsèquement multithread, mais ...
Periata Breatta
5
... il est tout à fait possible d'utiliser un Promiseavec un seul thread (et en fait c'est l'environnement précis pour lequel ils ont été initialement conçus: les applications javascript n'ont généralement qu'un seul thread, vous ne pouvez donc pas les implémenter Future). Promiseest donc beaucoup plus léger et efficace que Future, mais Futurepeut être utile dans des situations plus complexes et nécessitant une coopération entre des threads qui ne peuvent pas être facilement arrangés en utilisant Promises. Pour résumer: Promiseest un modèle push, tandis que Futureest un modèle pull (cf Iterable vs Observable)
Periata Breatta
@PeriataBreatta Même dans un environnement à un seul thread, il doit y avoir quelque chose remplissant la promesse (qui fonctionne généralement comme un thread différent, par exemple, un XMLHttpRequest). Je ne crois pas à la revendication d'efficacité, avez-vous des chiffres? +++ Cela dit, une très belle explication.
maaartinus
1
@maaartinus - oui, quelque chose doit tenir la promesse, mais cela peut (et en fait, dans de nombreux cas) se faire en utilisant une boucle de niveau supérieur qui interroge les changements d'état externe et résout les promesses liées aux actions qui ont été terminées. En termes d'efficacité, je n'ai pas de chiffres précis pour Promises en particulier, mais notez que faire appel getà une solution non résolue Futureimpliquera nécessairement 2 changements de contexte de threads, ce qui, au moins quelques années auparavant, nécessiterait probablement une cinquantaine de nous .
Periata Breatta
@PeriataBreatta En fait, votre commentaire devrait être la solution acceptée. Je cherchais une explication (pull / push, single / multi-thread) comme la vôtre.
Thomas Jacob
5

Pour le code client, Promise permet d'observer ou de joindre un rappel lorsqu'un résultat est disponible, alors que Future attend le résultat et continue. Théoriquement tout ce qui est possible de faire avec les futurs ce qui peut être fait avec les promesses, mais en raison de la différence de style, l'API résultante pour les promesses dans différentes langues facilite le chaînage.

user2562234
la source
2

Aucune méthode définie dans l'interface Future, uniquement la méthode get, elle est donc en lecture seule. À propos de CompletableFuture, cet article peut être utile. completablefuture

Jacky
la source