J'ai plusieurs méthodes CompletionStage
que j'aimerais enchaîner. Le problème est que le résultat du premier déterminera si les suivants doivent être exécutés. À l'heure actuelle, le seul moyen d'y parvenir semble être de passer des arguments "spéciaux" à la prochaine variable CompletionStage
, de sorte qu'elle n'exécute pas le code complet. Par exemple:
public enum SomeResult {
RESULT_1,
RESULT_2,
RESULT_3
}
public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) {
return CompletableFuture.supplyAsync(() -> {
// loooooong operation
if (someCondition)
return validValue;
else
return null;
}).thenCompose(result -> {
if (result != null)
return someMethodThatReturnsACompletionStage(result);
else
return CompletableFuture.completedFuture(null);
}).thenApply(result -> {
if (result == null)
return ChainingResult.RESULT_1;
else if (result.someCondition())
return ChainingResult.RESULT_2;
else
return ChainingResult.RESULT_3;
});
}
Puisque tout le code dépend de la première someCondition
(si c'est false
, le résultat sera RESULT_1
; sinon, tout le code devrait être exécuté), cette construction me semble un peu moche. Existe-t-il un moyen de décider si les méthodes 2nd (thenCompose(...)
) et 3rd (thenApply(...)
) doivent être exécutées?
Vous pouvez le faire comme ça:
public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) {
CompletableFuture<SomeResult> shortCut = new CompletableFuture<>();
CompletableFuture<ResultOfFirstOp> withChain = new CompletableFuture<>();
CompletableFuture.runAsync(() -> {
// loooooong operation
if (someCondition)
withChain.complete(validValue);
else
shortCut.complete(SomeResult.RESULT_1);
});
return withChain
.thenCompose(result -> someMethodThatReturnsACompletionStage(result))
.thenApply(result ->
result.someCondition()? SomeResult.RESULT_2: SomeResult.RESULT_3)
.applyToEither(shortCut, Function.identity());
}
Au lieu d'un CompletableFuture
, nous en créons deux, représentant les différents chemins d'exécution que nous pourrions emprunter. L’opération loooooong est alors soumise comme exécutable et complétera délibérément l’une de ces CompletableFuture
. Les étapes de suivi sont chaînées à l'étape représentant la condition remplie, puis les deux chemins d'exécution se rejoignent à la dernière étape applyToEither(shortCut, Function.identity())
.
Le shortCut
future a déjà le type du résultat final et sera complété par le RESULT_1
, résultat de votre chemin null
passing, ce qui entraînera la fin immédiate de toute l'opération. Si vous n’aimez pas la dépendance entre la première étape et la valeur réelle du raccourci, vous pouvez la rétracter comme ceci:
public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) {
CompletableFuture<Object> shortCut = new CompletableFuture<>();
CompletableFuture<ResultOfFirstOp> withChain = new CompletableFuture<>();
CompletableFuture.runAsync(() -> {
// loooooong operation
if (someCondition)
withChain.complete(validValue);
else
shortCut.complete(null);
});
return withChain
.thenCompose(result -> someMethodThatReturnsACompletionStage(result))
.thenApply(result ->
result.someCondition()? SomeResult.RESULT_2: SomeResult.RESULT_3)
.applyToEither(shortCut.thenApply(x -> SomeResult.RESULT_1), Function.identity());
}
Si votre troisième étape n’est pas exemplaire, mais ressemble exactement à celle présentée dans la question, vous pouvez la fusionner avec l’étape de jonction du chemin de code:
public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) {
CompletableFuture<ResultOfSecondOp> shortCut = new CompletableFuture<>();
CompletableFuture<ResultOfFirstOp> withChain = new CompletableFuture<>();
CompletableFuture.runAsync(() -> {
// loooooong operation
if (someCondition)
withChain.complete(validValue);
else
shortCut.complete(null);
});
return withChain
.thenCompose(result -> someMethodThatReturnsACompletionStage(result))
.applyToEither(shortCut, result -> result==null? SomeResult.RESULT_1:
result.someCondition()? SomeResult.RESULT_2: SomeResult.RESULT_3);
}
alors nous ne faisons que sauter la deuxième étape, l'invocation someMethodThatReturnsACompletionStage
, mais cela peut encore représenter une longue chaîne d'étapes intermédiaires, le tout étant ignoré sans qu'il soit nécessaire de déployer un saut manuel via nullcheck.
Par souci d'exhaustivité, j'ajoute une nouvelle réponse
Bien que la solution proposée par @Holger fonctionne à merveille, c'est un peu étrange pour moi. La solution que j'ai utilisée implique de séparer différents flux dans différents appels de méthode et de les chaîner avec thenCompose
:
public enum SomeResult {
RESULT_1,
RESULT_2,
RESULT_3
}
public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) {
return CompletableFuture.supplyAsync(() -> {
// loooooong operation
if (someCondition)
return operateWithValidValue(value);
else
return CompletableFuture.completedValue(ChainingResult.RESULT_1);
})
.thenCompose(future -> future);
public CompletionStage<SomeResult> operateWithValidValue(... value) {
// more loooong operations...
if (someCondition)
return CompletableFuture.completedValue(SomeResult.RESULT_2);
else
return doFinalOperation(someOtherValue);
}
public CompletionStage<SomeResult> doFinalOperation(... value) {
// more loooong operations...
if (someCondition)
return CompletableFuture.completedValue(SomeResult.RESULT_2);
else
return CompletableFuture.completedValue(SomeResult.RESULT_3);
}
NOTE: J'ai changé l'algorithme de la question dans le but d'une réponse plus complète
Toutes les opérations longues pourraient être potentiellement intégrées à un autre CompletableFuture.supplyAsync
avec peu d'effort