2016-11-21 3 views
2

J'essaye de retourner un CompletableFuture qui retournera une réponse d'Amazon. Mon code vérifie d'abord si une réponse est mise en cache localement. Si c'est le cas, il renvoie la réponse, sinon il appelle Amazon. [Note: La version réelle mettra également en cache la réponse reçue d'Amazon, mais je n'ai pas inclus cela car le code est déjà assez compliqué.]Éviter l'état de copie d'un CompletableFuture

Y at-il un moyen de changer l'implémentation de ma méthode callAmazon (ou autrement réorganiser le code) de sorte que je ne dois pas "manuellement" copier l'état de la réponse de amazonApi à la finalResponse?

Je ne veux pas retourner cacheCheck directement parce que je ne veux pas que l'appelant puisse le complete() il.

public CompletableFuture<Response> fetchProductList() { 
    CompletableFuture<Response> finalResponse = new CompletableFuture<>(); 
    CompletableFuture<Response> cacheCheck = //... 

    // First, see if we have a cached copy 
    cacheCheck.whenComplete((response, throwable) -> { 
     if (throwable == null) { 
      // Cache hit. Return the cached response 
      finalResponse.complete(response); 
     } else { 
      // Cache miss. Call Amazon 
      callAmazon(finalResponse); 
     } 
    }); 
    return finalResponse; 
} 

private void callAmazon(CompletableFuture<Response> finalResponse) { 
    CompletableFuture<Response> amazonApi = //... 
    amazonApi.whenComplete((response, throwable) -> { 
     // Copy the state to the `finalResponse` 
     if (throwable == null) { 
      finalResponse.complete(response); 
     } else { 
      finalResponse.completeExceptionally(throwable); 
     } 
    }); 
} 
+0

Check out .thenCompose: http://download.java.net/lambda/b88/docs/api/java/util/concurrent/CompletableFuture.html#thenCompose(java.util.function.Function) –

+0

Salut Alejandro, j'ai essayé de penser comment 'alorsCompose' pourrait être utilisé dans cette situation, mais je ne peux pas comprendre comment cela fonctionnerait. Je ne pense pas que vous pourriez esquisser un code pourrait vous? –

+0

Pourquoi vous embêtez-vous à propos de l'appelant appelant 'complete()'? Allez-vous partager cette 'CompletableFuture' entre plusieurs appels? Quoi qu'il en soit, vous pouvez facilement masquer la méthode en retournant un ['CompletionStage'] (https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html) à la place (mais' toCompletableFuture() 'l'affichera). –

Répondre

0

Ce qui rend votre exigence si complexe est le fait que cacheCheck peut déclencher une exception.

Ce que je ferais dans votre cas est de refactoriser le cache pour fournir soit null si la valeur n'a pas été trouvée dans le cache, soit le Response réel, si la valeur était dans le cache.

De plus, je modifierais callAmazon retourner directement le CompletableFuture:

private CompletableFuture<Response> callAmazon() { 
    CompletableFuture<Response> amazonApi = //... 
    return amazonApi; 
} 

De cette façon, vous pouvez utiliser thenCompose:

final CompletableFuture<Response> cacheCheck = //... 
final CompletableFuture<Response> amazonResponse = callAmazon(); 

final CompletableFuture<Response> finalResult = 
     cachedResponse.thenCompose(cacheResult -> { 
      return cacheResult == null ? amazonResponse : CompletableFuture.completedFuture(cacheResult); 
    }); 

Si vous vraiment besoin de lancer une exception à partir du cache , vous pouvez utiliser exceptionally pour convertir l'exception en une valeur nulle, puis utilisez thenCompose pour décider si vous utilisez la valeur de cache ou appelez Amazon:

final CompletableFuture<Response> finalResult = cachedResponse.exceptionally(e -> { 
     return null; 
    }).thenCompose(cacheResult -> { 
     return cacheResult == null ? amazonResponse : CompletableFuture.completedFuture(cacheResult); 
    });