Search code examples
javareactive-programmingvert.xevent-driven

How to block thread to wait for response in vert.x?


I have a situation where I call an external API A and use its response to feed to request of API B and call it and afterwards return the response to caller of API A. Something like below

   method(){
    response = call API A
    }

    method_for_API_A(){
      handler() ->{
      API_B
      }
    return response;
    }

    method_for_API_B(){
    //code to call API B
    }

What I am facing here is that API A method is returning response without waiting for getting response from B.

I checked about executeBlocking method of vert.x and also tried of using 'blocking queue' but unable to achieve what I am intended to do. Can someone please direct me to correct way of doing it.Thanks in advance.

EDIT: Just to explain the exact scenario

Class MyClass{
 public Response method_A (Request request){
 String respFromApiA = Call_API_A(request) ;  // STEP 1
 Response respFromApiB = Call_API_B(request, respFromApiA); // STEP 2
 Print(respFromApiB)   // PRINT FINAL Response
 return respFromApiB; // STEP 3
}

String Call_API_A(Request request){
// Implementation
Print(string);  // PRINT API A response
return string
}

Response Call_API_B(Response response){
// Implementation
Print(response);  // PRINT API B response
return response;
}

}

I am using vert.x framework with Java. Now what happens during execution is, flow comes to STEP 1, initiate API A call. Goes to STEP 2 (without waiting for 'respFromApiA') and makes call to API B (which fails eventually because respFromApiA is NULL). And finally flow goes to STEP 3 and return from here. (without waiting for outcomes of API A and API B). If we see the print order it will be something like this

PRINT FINAL Response
PRINT API A response
PRINT API B response

What I am trying to achieve?

Wait for API A response.
Make call to API B. Wait for API B response.
Return response got from API B.

I hope I am able to make clear this time. Please let me know if you need further inputs.


Solution

  • Vert.x is highly asynchronous. Most operations will in fact return immediately but their results will be available to a Handler at a later point in time. So far so good. If I understand you correctly than you need to call B in the Handler of A. In this case A needs to be finished and the result would be available before you call B:

    callA(asyncResultA -> {
      System.out.println("Result A: " + asyncResultA.result());
    
      callB(asyncResultB -> {
        System.out.println("Result B:" + asyncResultB.result());
      });
    });
    

    But what you are trying is to make something asynchronous synchron. You can not and should not try to make a asynchronous result available in the main program flow — that wouldn't work.

    String respFromApiA = Call_API_A(request); // STEP 1
    Response respFromApiB = Call_API_B(request, respFromApiA); // STEP 2
    Print(respFromApiB); // PRINT FINAL Response
    return respFromApiB; // STEP 3
    

    Call_API_A can't really return a result because it's calculated asynchronously. The result is only available for the Handler of Call_API_A (see my example above). Same for Call_API_B – so you can't return the result of Call_API_B. The caller of your class would also need to call your class with a Handler.

    Now some additional information. In your case you have the problem that multiple asynchronous results depend on each other. Vert.x provides a much more convenient way to handle asynchronous results — the so called Futures. A Future (sometimes called Promise but in the Java world they are called Future) is a placeholder for results of asynchronous calls. Read about them in the documentation.

    With a Future you can do something like this:

    Future<...> callAFuture = Future.future();
    callA(asyncResultA -> {
      if (asyncResultA.succeeded()) {
        System.out.println("A finished!");
        callAFuture.complete(asyncResultA.result());
      } else {
        callAFuture.fail(asyncResultA.cause());
      }
    });
    

    So instead of trying to return the asynchronous result of B in a synchronous way you should return a Future so the callee of your class could register for the asynchronous result of both A and B.

    I hope this helps.

    Edit: Future as return value

    Lets say you want to wrap callA with so you can work with a Future. You could do it this way:

    public Future<String> doSomethingAsync() {
      Future<String> callAFuture = Future.future();
    
      // do the async stuff
      callA(asyncResultA -> {
        if (asyncResultA.succeeded()) {
          System.out.println("A finished!");
          callAFuture.complete(asyncResultA.result());
        } else {
          callAFuture.fail(asyncResultA.cause());
        }
      });
    
      // return Future with the asyncResult of callA
      return callAFuture;
    }
    

    The Caller of this function can use the Future like this:

    Future<String> doSomethingFuture = doSomethingAsync();
    doSomethingFuture.setHandler(somethingResult -> {
      // ... doSomethingAsync finished
    });
    

    It also possible to compose multiple Future if you want to do them concurrently but they don't dependent on each other:

    CompositeFuture.all(futureA, futureB).setHandler(connections -> {
      // both Futures completed
    });
    

    If you work in a asynchronous environment like Vert.x most of time you work with a soon-to-be-available-result aka Future. And in the Handler of a Future you often do another asynchronous call. You wrap a Future with a Future like the example of callB in callA's Handler.

    How would you return the asynchronous result of a Future as an HTTP Response? Like this:

    router.route("/").handler(routingContext -> {
      HttpServerResponse response = routingContext.response();
    
      Future<String> future = doSomethingAsync();
      future.setHandler(somethingResult -> {
        if (somethingResult.succeeded()) {
          response
            .end(somethingResult.result());
        } else {
          routingContext.fail(500);
        }
      });
    });