web-dev-qa-db-fra.com

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

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

222
user1170330

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

Un avenir qui peut être explicitement complété (en définissant sa valeur et son statut), et qui peut être utilisé en tant que CompletionStage, prenant en charge des fonctions dépendantes et des actions qui se déclenchent après 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);
124
assylias

(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. Si quelqu'un d'autre vous fait une promesse, vous devez attendre de voir s'ils l'honorent à l'avenir ") résume assez bien, mais quelques explications peuvent être utiles.

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 goyave SettableFuture peuvent être considérés comme des promesses, car leur valeur peut être définie ("terminé"), mais ils implémenter l'interface Future, il n'y a donc aucune différence pour le client.

Le résultat du futur sera défini 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 un 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'autre part, le résultat d'une promesse peut être défini par "vous" (ou en fait par n'importe qui) à tout moment car il dispose d'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 vous la remplissez plus tard, à votre guise.

Notez que CompletableFuture n’est pas une promesse "pure", il peut être initialisé avec une tâche identique à celle de FutureTask et sa fonctionnalité la plus utile est l’enchaînement sans relation des étapes de traitement.

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

Spring et EJB 3.1 ont tous deux une classe AsyncResult, similaire aux promesses Scala/C++. AsyncResult implémente Future, mais il ne s'agit pas d'un futur réel: les méthodes asynchrones de Spring/EJB renvoient un objet Future différent en lecture seule par le biais de la magie de l'arrière-plan, et ce deuxième "réel" avenir peut être utilisé par le client pour accéder au résultat.

121
lbalazscs

Je suis conscient qu'il y a déjà une réponse acceptée, mais j'aimerais néanmoins ajouter mes deux sous:

TLDR: Future et Promise sont les deux côtés d'une opération asynchrone: consommateur/appelant vs producteur/implémenteur.

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

Pensez maintenant à la manière dont cette méthode API est réellement implémentée: Le implémenteur doit renvoyer un Future immédiatement. Ils sont responsables de la réalisation de cet avenir dès que le calcul est terminé (ils le sauront car il met en œuvre la logique de répartition ;-)). Ils utiliseront un Promise/CompletableFuture: Construisez et renvoyez immédiatement le CompletableFuture et appelez complete(T result) une fois le calcul effectué.

95
Rahel Lüthy

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

Supposons que vous ayez une mère et que vous lui demandiez de l'argent.

Maintenant, vous trompez votre mère en vous faisant une promesse de don, elle vous donne cet objet de promesse, mais elle n’est pas vraiment téméraire pour le remplir:

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

Tu es heureuse, tu cours à remercier ta maman:

promise.thenAccept(u->System.out.println("Thank you mom for $" + u ));

Mais votre père interfère et avorte généralement les plans de maman et complète la promesse (fixe sa valeur!) Avec une contribution bien moindre, comme le font les pères, très résolument, pendant que maman ouvre lentement son sac à main (remarquez Thread.sleep (...)):

promise.complete(10); 

La sortie de cela est:

Thank you mom for $10

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

CompletableFuture<Integer> promise...

Vous avez créé un tel événement, en acceptant sa promesse et en annonçant votre intention de remercier votre mère:

promise.thenAccept...

A ce moment, maman a ouvert son sac à main ... mais très lentement ...

et le père est intervenu beaucoup plus rapidement et a complété la promesse au lieu de votre mère:

promise.complete(10);

Avez-vous remarqué un exécuteur que j'ai écrit explicitement? Intéressant, que si vous utilisez plutôt l'exécuteur implicite par défaut (commonPool) et que le père ne soit pas à la maison, seulement avec sa "bourse lente", alors sa promesse sera seulement accomplie, si le programme vit plus longtemps que sa mère n'a besoin d'obtenir de l'argent le sac a main. Je veux dire que l'exécuteur par défaut agit comme un "démon". Je n'ai pas trouvé une bonne description de ce fait ...

45
Vladimir Nabokov

Je ne suis pas sûr que cela puisse être une réponse, mais si je vois ce que les autres ont dit à quelqu'un, vous aurez peut-être l’impression que vous avez besoin de deux abstractions distinctes pour ces deux concepts, de sorte que l’un d’eux (Future) ne soit qu’une lecture seule. vue de l'autre (Promise) ... mais en réalité 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é à l'aide de la méthode then 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 .... alors (fonction (résultat) {.... de celui-ci).

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

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

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

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

7
bodrin

Pour le code client, Promise permet d'observer ou de joindre un rappel lorsqu'un résultat est disponible, tandis que Future doit attendre le résultat, puis continuer. Théoriquement, tout ce qu'il est possible de faire avec des contrats à terme et des promesses, mais en raison de la différence de style, l'API résultante pour les promesses dans différentes langues facilite l'enchaînement.

2
user2562234

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

2
Jacky