2

I have a future that would ideally take two parameters coming from two other futures. For this I have .thenCombine(), the trick here is that the second future needs the result of the first one.

Let's say:

  • I have futures A, B and C
  • Future B needs the result of future A
  • Future C needs the result of future A and B

I would like to have something like:

CompletableFuture<Customer> customerFuture = CompletableFuture.supplyAsync(() -> findCustomer(123));
CompletableFuture<Shop> shopFuture         = CompletableFuture.supplyAsync((customer) ->getAllAccessibleShops(customer));
CompletableFuture<Route> routeFuture       = customerFuture.thenCombine(shopFuture, (cust, shop) -> findRoute(cust, shop));

Of course thenCombine() is not what I'm looking for and the code above looks dumb because I shouldn't need the customer afterwards, but this is only an example.

Is there a way to achieve this?

Martin GOYOT
  • 286
  • 1
  • 4
  • 12
  • If the steps have to be done sequentially why are they in separate Futures? – Eddie Curtis Nov 29 '16 at 11:02
  • @jimmycarr could you elaborate on this? I'm really interested – Martin GOYOT Nov 29 '16 at 11:05
  • The point of a future is to do some processing in the background and then allow you to get the result later on. If future's B and C depend on the other Futures before them, then putting it all in one future is just as quick as putting it in three separate Futures which all depend on each other. – Eddie Curtis Nov 29 '16 at 11:23
  • Okay I understand, makes sense. So I suppose I will change the way I do this. Now just out of curiosity, is there something that could make it work anyway? I mean having this sequential dependency? – Martin GOYOT Nov 29 '16 at 12:23
  • Yes you can make it work the way you originally had in mind. If you call [Future.get()](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Future.html#get()) on each of the futures you can wait for it to finish processing and get the result which can then be passed into the constructor of the next future. – Eddie Curtis Nov 29 '16 at 12:37
  • Okay, now I really see why this is broken. Doing the .get() just breaks the overall idea. Thanks for the light! – Martin GOYOT Nov 29 '16 at 12:55
  • You do not need to call `.get()`, and in most cases you should not call it when using `CompletableFuture`. Your usage of `thenCombine()` is fine, the only problem in your code is in the creation of `shopFuture`. You should use `thenApply[Async]()` for that. Of course their execution will be sequential, but maybe there is additional processing you would like to do with the result of A or B that could be performed in parallel to B or C, and then this would become more useful. – Didier L Nov 30 '16 at 13:50
  • Oh, I see, like have `A, B=A.thenApply((x) -> {}) and C = A.thenCombine(B, (x,y) -> {})` ? – Martin GOYOT Nov 30 '16 at 15:28
  • Yes, so it's almost what you did. Do you want I post it as an answer? (you should @mention people so they get notifications, otherwise only the OP is notified) – Didier L Dec 02 '16 at 14:04
  • oh sorry @DidierL I didn't know this. Yes can you post it as an answer so that I can accept it? – Martin GOYOT Dec 02 '16 at 15:30

1 Answers1

3

Your solution is correct, the only issue is in the declaration of shopFuture. You should use thenApply[Async]() so that it can access the result of the first one:

CompletableFuture<Customer> customerFuture = CompletableFuture.supplyAsync(() -> findCustomer(123));
CompletableFuture<Shop> shopFuture         = customerFuture.thenApply((customer) -> getAllAccessibleShops(customer));
CompletableFuture<Route> routeFuture       = customerFuture.thenCombine(shopFuture, (cust, shop) -> findRoute(cust, shop));

Note that the execution order remains sequential since shopFuture requires the result of customerFuture, and routeFuture requires the result of shopFuture. However if you have additional work to do with the Customer or the Shop, you could use additional thenApply[Async] calls to run them.

If you don't have anything to do with those results, you might want to group all the 3 calls into a single supplyAsync():

CompletableFuture<Route> customerFuture = CompletableFuture.supplyAsync(() -> {
   Customer customer = findCustomer(123));
   Shop shop = getAllAccessibleShops(customer));
   return findRoute(customer, shop)
});

See also CompletableFuture, supplyAsync() and thenApply() for the difference in behaviour between the two.

Community
  • 1
  • 1
Didier L
  • 18,905
  • 10
  • 61
  • 103