span
span

Reputation: 5628

Chaining in CompletableFuture to return first value

I have some code that saves an entity to a database using spring-data and then performs some other work foo() and bar() that needs the id from the entity that has been saved. It looks like this:

private CompletableFuture<Void> save(MyEntity me) {
    CompletableFuture<Void> future = ContextAwareCompletableFuture
        .runAsync(() -> repository.save(me))
        .thenRunAsync(() -> foo(me))
        .thenRunAsync(() -> bar(me));
    return future;
}

private Foo foo(MyEntitiy me) {
    // Use the identifier for me to update some foo in another world
}

private Bar bar(MyEntitiy me) {
    // Use the identifier for me to update some bar in at another time
}

Now, I do not want to return void from my save method. I want to return a MyEntity so I tried:

private CompletableFuture<MyEntity> save(MyEntity me) {
    CompletableFuture<MyEntity> future = ContextAwareCompletableFuture
        .runAsync(() -> repository.save(me))
        .thenRunAsync(() -> foo(me))
        .thenRunAsync(() -> bar(me));
    return future;
}

This does not work since runAsync returns void. My method repository.save() returns the object I wish to return but that call is at the beginning of the chain. I need to save the object before I can do my foo and bar.

So next thing I tried is:

private CompletableFuture<MyEntity> save(MyEntity me) {
    CompletableFuture<MyEntity> future = ContextAwareCompletableFuture
        .supplyAsync(() -> repository.save(me))
        .thenApplyAsync((e) -> baz(e);
    return future;
}

private MyEntity baz(MyEntitiy me) {
    foo(me);
    bar(me);
    return me;
}

Now, that seems wrong to me. Foo and Bar will now have to be executed during the same stage and they might take some time.

How can I return the object saved in repository.save() after foo and bar has finished properly?

Upvotes: 2

Views: 1276

Answers (2)

acelent
acelent

Reputation: 8135

If foo and bar may run concurrently, you may choose to chain on save instead of sequencing them:

private CompletableFuture<MyEntity> save(MyEntity me) {
    CompletableFuture<MyEntity> future = ContextAwareCompletableFuture
        .supplyAsync(() -> repository.save(me));
    CompletableFuture<Void> fooFuture = future
        .thenAcceptAsync((e) -> foo(e));
    CompletableFuture<Void> barFuture = future
        .thenAcceptAsync((e) -> bar(e));
    return future
        .thenCombine(fooFuture, (result, fooResult) -> result)
        .thenCombine(barFuture, (result, barResult) -> result);
}

Note I used thenAcceptAsync instead of thenRunAsync to avoid capturing me. I avoided the capture in the end as well.

We can avoid one thenCombine if we return the entity on fooFuture and barFuture:

private CompletableFuture<MyEntity> save(MyEntity me) {
    CompletableFuture<MyEntity> future = ContextAwareCompletableFuture
        .supplyAsync(() -> repository.save(me));
    CompletableFuture<MyEntity> fooFuture = future
        .thenApplyAsync((e) -> { foo(e); return e; });
    CompletableFuture<MyEntity> barFuture = future
        .thenApplyAsync((e) -> { bar(e); return e; });
    return fooFuture
        .thenCombine(barFuture, (fooResult, barResult) -> fooResult);
}

Upvotes: 2

Joe C
Joe C

Reputation: 15714

You could chain with a method that does stuff and returns the input:

.thenApply(e -> { foo(e); return e; }

Upvotes: -1

Related Questions