2

I have an async function in java. I want to make async calls to a number of APIs (in parallel) then create a response. Since I need the different results, I though to save them in the class. I have helper functions to make the calls to the external APIs (classes with a call function which return CompletableFuture<List<T>>). I can do this like this:

public class MyAction{
   private List<Bar> bars;
   private List<Baz> bazs;

   public CompletableFuture<Foo> run() {
       CompletableFuture<Void> fetchBars = new BarFetcher().call().thenAccept(this::populateBars);
       CompletableFuture<Void> fetchBazs = new BazFetcher().call().thenAccept(this::populateBazs);

       return CompletableFuture.allOf(fetchBars, fetchBazs).thenApply(this::getFoo);
   }

   private void populateBars(final List<Bar> bars) {
       this.bars = bars;
   }

   private void populateBaz(final List<Baz> bazs) {
       this.bazs = bazs;
   }

   private Foo getFoo(final Void dummy) {
       return new Foo(bars, bazs);
   }
}

But I had to add an unnecessary parameter (final Void dummy) to the getFoo function to make it work. Can I avoid this? if should really just be getFoo() not getFoo(final Void dummy). Is there any way to wait for multiple futures to complete and then chain in another function without passing it directly any data?

Note: The example only has two initial calls to fetch data (fetchbars and fetchBazs) before the final processing (getFoo). However, I actually will have more than two. I currently have three, but it may grow by one or two more. I would like to run all the initial calls in parallel, then the final processing once all have completed.

Adam
  • 6,539
  • 3
  • 39
  • 65

2 Answers2

2

Use the right tool for the job. To combine two futures, just use

public CompletableFuture<Foo> run() {
    return new BarFetcher().call().thenCombine(new BazFetcher().call(),
        (List<Bar> bars, List<Baz> bazs) -> new Foo(bars, bazs));
}

or even simpler

public CompletableFuture<Foo> run() {
    return new BarFetcher().call().thenCombine(new BazFetcher().call(), Foo::new);
}
Holger
  • 285,553
  • 42
  • 434
  • 765
  • This looks like what I want. However, I had simplified my problem slightly in the example code - I actually have >2 calls I want to make to fetch the data i will then work with :). Currently three, but i expect it to grow. i googled a bit and couldn't find any clean ways to generalise this. There was some examples of chaining with `Pair<>` as the second param in the combines, but it got messy. Also, I want to run all the population requests in parallel, not run two and when they are done a third and so on. Is it possible to generalise this to more than two parallel calls initially? – Adam Dec 10 '18 at 16:23
  • When you use `thenCombine`, the futures *are* already running in parallel. The syntax does not scale well with a growing number of futures to combine, so you may prefer using `allOf` and live with the little nuisance of having to drop a `Void` parameter in the adapter function (compare to [this answer](https://stackoverflow.com/a/34005471/2711488)). For inputs of the same type, you could use the alternative `allOf` implementation of [this answer](https://stackoverflow.com/a/49939668/2711488), which returns a `CompletionStage>`. – Holger Dec 11 '18 at 08:18
  • Thanks. I think I'll stick with what I have for now - as long as it's a private function I can live with the `Void dummy`. – Adam Dec 12 '18 at 16:22
0

you could change the use of method reference to a lambda as follows:

return CompletableFuture.allOf(fetchBars, fetchBazs)
                        .thenApply(e -> getFoo());

then you don't have to introduce a dummy parameter.


Note, I am not that familiar with the CompletableFuture API so, there may be more suitable solutions out there.

Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
  • It is still there, just that you don't take it :) – NiVeR Dec 03 '18 at 22:16
  • @NiVeR true but isn't this what the OP is pretty much asking for "But I had to add an unnecessary parameter (final Void dummy) to the getFoo function to make it work. Can I avoid this? " so they can avoid having to create a dummy parameter by not using a method reference. anyway, I had a feeling there are better solutions out there which I believe #Holger has just posted. – Ousmane D. Dec 03 '18 at 22:30
  • Yeah, not sure. it makes the `getFoo()` definition cleaner, if it would ever be called from anywhere else. But given that it's private (for a reason - it will fail unless the population has run first) then it probably won't be :) But at the cost of introducing an otherwise unnecessary lambda – Adam Dec 10 '18 at 16:29