Search code examples
reactivecompletable-futurehelidondbclient

How to sequence a task to execute once all Single's in a collection complete


I am using Helidon DBClient transactions and have found myself in a situation where I end up with a list of Singles, List<Single<T>> and want to perform the next task only after completing all of the singles.

I am looking for something of equivalent to CompletableFuture.allOf() but with Single.

I could map each of the single toCompletableFuture() and then do a CompletableFuture.allOf() on top, but is there a better way? Could someone point me in the right direction with this?

--

Why did I end up with a List<Single>?

I have a collection of POJOs which I turn into named insert .execute() all within an open transaction. Since I .stream() the original collection and perform inserts using the .map() operator, I end up with a List when I terminate the stream to collect a List. None of the inserts might have actually been executed. At this point, I want to wait until all of the Singles have been completed before I proceed to the next stage.

This is something I would naturally do with a CompletableFuture.allOf(), but I do not want to change the API dialect for just this and stick to Single/Multi.


Solution

  • Single.flatMap, Single.flatMapSingle, Multi.flatMap will effectively inline the future represented by the publisher passed as argument.

    You can convert a List<Single<T>> to Single<List<T>> like this:

    List<Single<Integer>> listOfSingle = List.of(Single.just(1), Single.just(2));
    Single<List<Integer>> singleOfList = Multi.just(listOfSingle)
                                              .flatMap(Function.identity())
                                              .collectList();
    

    Things can be tricky when you are dealing with Single<Void> as Void cannot be instantiated and null is not a valid value (i.e. Single.just(null) throws a NullPointerException).

    // convert List<Single<Void>> to Single<List<Void>>
    Single<List<Void>> listSingle =
            Multi.just(List.of(Single.<Void>empty(), Single.<Void>empty()))
                 .flatMap(Function.identity())
                 .collectList();
    
    // convert Single<List<Void>> to Single<Void>
    // Void cannot be instantiated, it needs to be casted from null
    // BUT null is not a valid value...
    Single<Void> single = listSingle.toOptionalSingle()
                                    // convert Single<List<Void>> to Single<Optional<List<Void>>>
                                    // then Use Optional.map to convert Optional<List<Void>> to Optional<Void>
                                    .map(o -> o.map(i -> (Void) null))
                                    // convert Single<Optional<Void>> to Single<Void>
                                    .flatMapOptional(Function.identity());
    
    // Make sure it works
    single.forSingle(o -> System.out.println("ok"))
          .await();