4

An answer here quotes a table of all CompletableFuture methods, but it's not quite what I'm looking for, or perhaps I'm going about it wrong.

I'm looking for the CompletableFuture equivalent of Streams' peek(), so basically a thenApply that returns the input argument or a thenRun that doesn't return Void. There are two ways I can think of that both don't semantically accurately express my intention, but do the job:

(..)
.thenApply((a) -> {
   doSomething();
   return a;
}
(..)

and

(..)
.whenComplete((result, exception) -> {
   if (exception == null) {
      doSomething();
   }
}
(..)

Both take the input from the previous stage, allow me to perform an action and return with the same type to the next stage. Of these two the latter second approach limits my timing to when everything else is done, rather than async as soon as the previous necessary stage is complete.

I've also been searching for an identity function that takes a consumer function as argument, but it seems I would have to write that myself:

public static <T> Function<T, T> identityConsumer(Consumer<T> c) {
    return a -> { c.accept(a); return a; };
}
(..)
.thenApply(identityConsumer(a -> doSomething()));
(..)

So short of writing my own util functions, is there an elegant way of performing an action in an intermediate stage, where I don't have to return something, while keeping the stage's current type?

Benny Bottema
  • 11,111
  • 10
  • 71
  • 96
  • 2
    “_the […] second approach limits my timing to when everything else is done, rather than async as soon as the previous necessary stage is complete_” – I don’t really understand what you mean here, what is “_everything else_”? Both would be called at the same moment (well, except `thenApply()` won’t be called in case of exception). `whenComplete()` seems to be exactly what you are looking for. – Didier L Jan 17 '22 at 16:32
  • Hmm, I assumed after whenComplete wouldn't allow then's after it. However, whenComplete forces me to explicitly deal with exception cases, which is not what I want at this stage. – Benny Bottema Jan 17 '22 at 18:15
  • Well, if you want an equivalent to `peek()`, you should deal with exceptions as well since they don’t stop the execution of descendant stages – as opposed to the `Stream` API, in which any exception stops the whole pipeline. Anyway, the solutions mentioned here are all we have for now – Didier L Jan 17 '22 at 18:30

2 Answers2

1

I wanted to do this too, so I just made my own wrapper:

/** Converts a Consumer into an identity Function that passes through the input */
public static <T> Function<T, T> peek(Consumer<T> fn) {
    return (t) -> {
        fn.accept(t);
        return t;
    };
}

used like:

.thenApply(Functions.peek(SomeUtil::yourConsumerMethod));
Nicole
  • 32,841
  • 11
  • 75
  • 101
0

Unlike with Stream, you can make multiple function chains from a single future, so it is not necessary to do everything in a single chain of calls. I would approach your problem like so:

var f1 = CompletableFuture.supplyAsync(...);
f1.thenRun(() -> doSomething()); // this is your "peek"
var f2 = f1.thenApply(...); // continue your chain of operations here
MikeFHay
  • 8,562
  • 4
  • 31
  • 52
  • 2
    That's a good point and I hadn't thought of it. However, breaking up the chain like this kind of defeats the purpose of the fluent api. It's probably the canonical JDK solution, but not exactly 'elegant'. – Benny Bottema Jan 17 '22 at 16:02
  • But you don't have access to the result, which is the purpose of peeking. Even worse: **this doesn't work reliably!** If `doSomething()` takes longer/sleeps, then it may not finish before `f2` finishes, i.e. it may not finish at all or it may succeed even though `f2` fails. Don't do that. @Nicole has a much better solution. – rü- May 23 '23 at 16:15
  • The peek can succeed while the next function fails in Nicole's solution and in `Stream.peek` also. It's not clear that the order of operations matters for OP, but you're right that if it does then they should model that explicitly. – MikeFHay May 24 '23 at 10:05
  • I didn't say it correctly: what I should have said is that the second `thenApply` will run, even when the `thenRun` fails. And I think the order of operations is almost always relevant. E.g. the `peek` could be a verification... a very common use case. – rü- May 24 '23 at 11:05