Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

asynchronous WS calls and await() outside of a controller

Is there a way to use the await() mechanism outside of a controller?

I do not wish to have code making the asynchronous call in the controller, but instead have that code in a service class that can be reused by multiple controllers however, there is no way to call await outside of a controller since that method is protected.

So for example in the controller:

   ServiceClass service = new My ServiceClass();
   MyObject myObject= service.getMeAnObject();
   render(myObject);

And the service class:

   public class ServiceClass
   {
      ...
      public MyObject getMeAnObject()
      {
         String url = "http://...";
         Promise<HttpResponse> promise = url(url).getAsync();

         // do something here similar to await in a controller
         // that suspends the code waiting for the response
         // to be retrieved

         HttpResponse response = promise.get();
         return buildMyObjectFromResponse(reponse);
      }
      ...
   }

Is there a way to achieve something like that?

Thanks for your help.


Edit: I followed Pere's advice and made the service class implement Controller, it works however, it is necessary that the controller using it be enhanced. The only way I found out to get that done is at least invoke once the await method() in the calling controller class.

However, I still have not verified that the code is actually suspended.


Edit 2: One suggestion I got from the google group is that I should really try and do the await in the controller, so maybe an better solution would be for the service to return a Promise and have the controller wait for this but then there's no way for me todo that is there?

So for example in the controller:

  ServiceClass service = new My ServiceClass(); 
  Promise<MyObject> promise = service.getMeAnObject(); 
  MyObject myObject = await(promise); 
  render(myObject); 

And the service class:

  public class ServiceClass 
  { 
     ... 
     public Promise<MyObject> getMeAnObject() 
     { 
        String url = "http://..."; 
        // Somehow Build a Promise<MyObject> 
     } 
     ... 
  } 

}

like image 266
Dan Serfaty Avatar asked Dec 14 '11 20:12

Dan Serfaty


3 Answers

So far, this is the best approach I have found I think, unless there is a problem with managing separate thread pools within the Play instance.

I have taken example on what Play does in the WSAsync.WSAsyncRequest class and this seems to work.

In essence, the await is done within the controller, but the service class returns a Promise of the object I expected the service to return which is fine because I don't know the details of how that object was retrieved at the controller level.

In the controller:

ServiceClass service = new My ServiceClass(); 
Promise<MyObject> promise = service.getMeAnObject(); 
MyObject myObject = await(promise); 
render(myObject); 

And the service class:

public class ServiceClass 
{ 
    public Promise<MyObject> getMeAnObject() 
    { 
        String url = "http://..."; 
        return execute(WS.url(url).getAsync()); 
    } 

    private Promise<MyObject> execute(final Promise<HttpResponse> promise) 
    { 
        try 
        { 
            final Promise<MyObject> smartFuture = new Promise<MyObject>(); 
            ScheduledThreadPoolExecutor executor = MyAppThreadPools.getServiceThreadPool(); 

            executor.submit(new Runnable() 
                { 
                    @Override 
                    public void run() 
                    { 
                        try 
                        { 
                            smartFuture.invoke(new MyObject(promise.get())); 
                        } 
                        catch (Throwable e) 
                        {                
                            smartFuture.invokeWithException(e); 
                        } 
                    } 
                });

            return smartFuture; 
        } 
        catch (Exception e) 
        { 
            throw new RuntimeException(e); 
        } 
    } 
}

So this seems to solve my problem for now.

Thanks all.

like image 101
Dan Serfaty Avatar answered Nov 17 '22 01:11

Dan Serfaty


As you mentioned await is only accessible from a Controller.

Make the Service class extend Controller, create a number of protected support methods in there and use it as parent class for the Controllers.

That way you can share the implementation across controllers, while having access to all the controller methods (await, render, etc).

like image 45
Pere Villega Avatar answered Nov 16 '22 23:11

Pere Villega


This is probably too late to help you :) but the way to do it is by using the Promise.onRedeem() method to invoke a new promise that is returned and awaited in the controller...

public Promise<Boolean> deauthorise() {
    final Promise<Boolean> promise = new Promise<>();

    WS.url("https://connect.stripe.com/oauth/deauthorize")
            .setParameter("client_secret", play.Play.configuration.getProperty("stripe.secretKey"))
            .setParameter("client_id", play.Play.configuration.getProperty("stripe.clientId"))
            .setParameter("stripe_user_id", this.stripeUserId)
            .postAsync()
            .onRedeem(new Action<Promise<HttpResponse>>() {
                @Override
                public void invoke(Promise<HttpResponse> result) {
                    HttpResponse response = result.getOrNull();
                    promise.invoke(response != null && response.success());
                }
            });
    return promise;
}
like image 24
tazmaniax Avatar answered Nov 16 '22 23:11

tazmaniax