0

I can't find a correct solution to this problem and I'm stuck. Let's say I have this method

    @GET
    @Path("/testingAsync")
    public Uni<List<String>> testingMutiny() {
        List<String> completeList = new ArrayList<>();
        completeList.add("hello");
        completeList.add("RestEasy");

        List<String> finalList = new ArrayList<>();

        completeList.forEach(e -> Uni.createFrom().item(e)
                .onItem().delayIt().by(Duration.ofMillis(10000))
                .map(value -> finalList.add(value.toUpperCase()))
                .subscribe().asCompletionStage());


        return Uni.createFrom().item(finalList);
    }

As you see the method is simple it just takes the values from 1 list and adds them to the second one but what's the problem? When you add the waiting .onItem().delayIt().by(Duration.ofMillis(10000)) the program will return an empty list and after a while, it will just update the list. I created this method to simulate a request that the response that has some delay in it.

Let's say you hit 2 URLs with 2 different Unis after that you try to combine them and return it as one Uni. The problem is if one of those 2 URLs delay for some reason we will return the list empty but I don't want that to happen I either want the list to be completed 100% or return an error if it takes a while.

What is the best approach to that? I understand that if you add await() you are blocking the main thread and you lose all the value of using the reactive library but still, I can't find a way for this to work

EDIT

I have found out that the external URL I try to call takes about 5 seconds to do the job so I want my code to stop when creating the Uni and continue after I have received an answer from the server. I have seen in their docs (here) That I can also call await.indefinitely but I receive The current thread cannot be blocked: vert.x-eventloop-thread-14. How do I wait for a response from the server?

EDIT 2

I understand that with strings it doesn't make sense my question so much so let's say I have the following one

    @GET
    @Path("/testingAsync")
    public Uni<List<Car>> testingMutiny() {

        //ALL THIS IS IN A FOR EACH FOR EVERY CAR

            //HIT ENDPOINT GET DOORS
            Uni<List<JsonObjectCar>> carDoorsUni = getDoors(variable1,
                    variable2, variable3);

            //HIT ENDPOINT GET WHEELS
            Uni<List<JsonObjectCar>> carWheelsUni = getWheels(variable1,
                    variable2, variable3);

            //HIT ENDPOINT GET WINDOWS
            Uni<List<JsonObjectCar>> carWindowsUni = getWindows(variable1,
                    variable2, variable3);

            Uni.combine()
                    .all()
                    .unis(carDoorsUni, carWheelsUni, carWindowsUni)
                    .combinedWith((carDoors, carWheels, carWindows) -> {
                        //Check if cardoors is present and set the doors into the car object
                        Optional.of(carDoors)
                                .ifPresent(val -> car.setDoors(val.getDoors()));
                        Optional.of(carWheels)
                                .ifPresent(val -> car.setWheels(val.getWheels()));
                        Optional.of(carWindows)
                                .ifPresent(val -> car.setWindows(val.getWindows()));

                        return car;
                    }).subscribe().with(e-> System.out.println("Okay it worked"));
            
          //END OF FOR EACH 
            
            
            //Return car (Should have been returned with new doors / wheels/ windows but instead its empty)
        return Uni.createFrom().item(car);

    }

As you see in the above code It should have hit some endpoints for doors / wheels / windows and set them into the variable car but what happens, in reality, is that the car is empty because one of those endpoints has been delayed so i return a car without those values inside it. I want to first update the car object and then actually return it

Theodosis
  • 792
  • 2
  • 7
  • 22
  • `Uni.createFrom().item(finalList)` creates a `Uni` that is already complete. You need to create a `Uni` that is incomplete (or "in progress"), return it, and when all the processing is done, complete. You can use e.g. an emitter: https://smallrye.io/smallrye-mutiny/1.7.0/tutorials/creating-uni-pipelines/#creating-unis-using-an-emitter-advanced Alternatively, you can create multiple `Uni`s (which you already do) and then join them: https://smallrye.io/smallrye-mutiny/1.7.0/guides/joining-unis/#joining-multiple-unis – Ladicek Nov 07 '22 at 14:26
  • After reading again the documentation i kinda understand how the emitter works but inside the documentation it never shows how he got the result and i can't seem to find any other documentation about this – Theodosis Nov 07 '22 at 15:11

3 Answers3

3

You could rewrite the method like this:

    @GET
    @Path("/testingAsync")
    public Uni<List<String>> testingMutiny() {
        List<Uni<String>> unis = new ArrayList<>();
        List.of("hello", "RestEasy").forEach( e -> {
            unis.add( Uni.createFrom().item( e )
                .onItem().delayIt().by( Duration.ofMillis( 10000 ) ) );
        } );

        return Uni.combine().all().unis( unis )
                .combinedWith( list -> (List<String>) list);
    }

Note that when you write reactive code, you want to avoid using .await().indefinetly. It shouldn't be needed anyway when using Quarkus, because it recognizes async API and interpret the results accordingly.

You also don't need to subscribe the Uni or Multi when using Quarkus, for the same reason.

Based on my previous example, you can rewrite your use case with endpoints as:

    @GET
    @Path("/testingAsync")
    public Uni<Car> testingMutiny() {
            Uni<List<JsonObjectCar>> carDoorsUni = getDoors(variable1, variable2, variable3);
            Uni<List<JsonObjectCar>> carWheelsUni = getWheels(variable1,variable2, variable3);
            Uni<List<JsonObjectCar>> carWindowsUni = getWindows(variable1,variable2, variable3);

            return Uni.combine()
                    .all()
                    .unis(carDoorsUni, carWheelsUni, carWindowsUni)
                    .combinedWith(list -> {
                        // Result of carDoorsUni
                        List<JsonObjectCar> carDoors = list.get(0);

                        // Result of carWheelsUni
                        List<JsonObjectCar> carWheels = list.get(1);

                        // Result of carWindowsUni
                        List<JsonObjectCar> carWindows = list.get(2);
                        
                        // Create a car instance with the previous results
                        Car car = createCar(...);

                        // You can also return a list of cars, but you need to change the return type of testingMutiny to Uni<List<Car>>
                        return car;
                    })
                    .invoke( () -> System.out.println("Okay it worked"));
    }
Davide D'Alto
  • 7,421
  • 2
  • 16
  • 30
  • Please see the updated question i have tried this with the strings and i can say it works but the problem is that if i have external URLs and the delay happens on their side it actually doesn't wait for it. I turned my objects into lists in order to do this but i didn't manage to understand it. I'm sorry for probably confusing you but i probably have missed something in the documentation. I was curious if adding Uni.Builders changed anything but still had the same result the car returned empty and after a while, my Uni has been run but its already too late – Theodosis Nov 09 '22 at 10:01
  • Your second example doesn't seem correct, but I gave you a second solution. Please, refrain from changing the question multiple times. Now you should have all the info you need to write your method. – Davide D'Alto Nov 09 '22 at 11:16
  • I tried to make my problem simple and you helped me a lot thanks for everything working perfectly. – Theodosis Nov 09 '22 at 11:41
  • Nice! It's never easy to use new APIs. Glad it works now. – Davide D'Alto Nov 09 '22 at 21:41
2

You return a list, but the asynchronous processing on the Uni is delayed, so your list will be empty.

You should try returning a Uni from the pipeline that you create (and also see collect(), toUni() to put into lists) instead of doing a subscription, collect the results and re-wrap into a Uni.

jponge
  • 510
  • 2
  • 4
  • In this example the delay there is on purpose in order to simulate a request where the response is delayed. What if my Uni there was a rest-client request and I wanted to combine it and after all the process is finished continue? This is what I'm missing. Another example is this `Uni.combine() .all() .unis(uni1,uni2,uni3)..combinedWith((uni1, uni2, uni3) -> { //do some actuall work there change some variables } ) ` in the above case if uni2 is delayed my method will just return empty object because combinedWith has not been run – Theodosis Nov 08 '22 at 12:48
  • We have disgusted this in the previous question I have asked here https://stackoverflow.com/questions/74317223/quarkus-mutiny-request-ignored-when-using-unis-combinedwith . This actually answered one stage of my question but after adding the subscribe I find out the combinedWith runs when I received an answer from the requests. I want this method to run and then continue I kinda want to add a block there because the responses are necessary in order to continue – Theodosis Nov 08 '22 at 13:42
0

With Multi and Stream we could come up with something like:

    @GET
    @Path("/testingAsync")
    public Multi<String> testingMutiny() {
        List<String> completeList = new ArrayList<>();
        completeList.add("hello");
        completeList.add("RestEasy");

        return Multi.createFrom().items(completeList.stream()).onItem()
                .transformToUni(value -> Uni.createFrom().item(value.toUpperCase()))
                .concatenate();
    }

Also is good to know about differences between Merging and Concatenating Streams

SidMorad
  • 954
  • 12
  • 19