Apr 4, 2013

Improving the GWT Async Callback

The core of GWT framework for async communication to the server is through the async callback interface. Its interface is rather simple, but unfortunately, as with a lot GWT development the simplicity is lost with the the amount of boiler plate code you are constantly writing. You have to write a service interface, an async service interface, service implementation, and a class (or anonymous class) that extends the AsyncCallback. Also, if you have any framework code you want to add to your async calls, that is just more boiler plate code you need to add and make sure that everyone follows/uses when writing the client side calls to the async callback.

Needless to say, in my 1.5 years of writing GWT applications, I’m sick and tired of boiler plate code. I know boiler plate code is a result of issues with the Java language, but it seems like with GWT it is even more than with other projects. If only Google was working on a Groovy to Javascript GWT Compiler. But, since I’m confined to using Java, I will look into one area to reduce boiler plate code, and that is with the asynchronous service calls.

What is the GWT Asynchronous Service Interface?

The Asynchronous Service Interface is nice and simple. GWT, as with other client side libraries, have hidden the complexity of writing your AJAX calls. It is done by extending, usually through an anonymous inner class, the AsyncCallback interface.

The interface is just this:

  1. public interface AsyncCallback<T> {
  2.   void onFailure(Throwable caught);
  3.   void onSuccess(T result);
  4. }

You then have two service interfaces the standard interface, and the async interface

Standard Service Interface

  1. @RemoteServiceRelativePath("gwt/MyService")
  2. public interface MyService {
  3.   MyData getData(String parameter);
  4. }

Async Service Interface
the return parameter is wrapped as a parameter in the AsyncCallback interface (shown above).

  1. public interface MyServiceAsync {
  2.   void getData(String parameter, AsyncCallback<MyData> callback);
  3. }

Calling the Service from GWT

  1. public class MyClass {
  3.   @Inject
  4.   private MyServiceAsync service;
  6.   public void someMethod() {
  7.     service.getData(new AsyncCallback<MyData>() {
  8.       void onSuccess(MyData data) {
  9.         //success code goes here.
  10.       }
  11.       void onFailure(Throwable throwable) {
  12.         // failure code goes here.
  13.       }
  14.     });
  15.   }
  16. }

The boiler-plate code

There is code that I end up writing around calling all of my AJAX services. I would like to add this code w/out having boiler-plate code all around my application.

  • I want to log each call including the time needed to make the call
  • I want certain exceptions to be handled in a uniform way across all of my calls
  • I want the ability to retry the asynchronous service call.

If we look at the logging, adding code an existing Async Callback is easy enough. However, when you have many anonymous classes and more complex logging; it all adds up.

  1. public class MyClass {
  3.   @Inject
  4.   private MyServiceAsync service;
  6.   public void someMethod() {
  7.     long start = System.currentTimeMillis();
  8.     service.getData(new AsyncCallback<MyData>() {
  9.       void onSuccess(MyData data) {
  10.         Log.debug("time : " + (System.currentTimeMillis() - start);
  11.         //success code goes here.
  12.       }
  13.       void onFailure(Throwable throwable) {
  14.         Log.debug("time (failure) : " + (System.currentTimeMillis() - start);
  15.         // failure code goes here.
  16.       }
  17.     });
  18.   }
  19. }

Now, every time I make a service call, I need to add this boilerplate code.

The GwtCallbackWrapper

The solution is to wrap the AsyncCallback in another class, and use that class to make your async calls. Extending the AsyncCallback interface doesn’t work, since you cannot wrap the method calls and you end up providing alternate methods, but it is an interface (everythings public) so extending it just makes things more confusing. Instead, create a wrapper class that has the same methods, but creates a simple AsyncCallback that ends up calling the methods on the wrapped class. Then add a new onCall(AsyncCallback callback) method that is used to start the invocation. Then you can swap out this new class for the AsyncCallback class and your anonymous inner classes are virtually unchanged. And by adding in the onCall method, it makes retries easy.

Here is the AsyncCallback replacing that wraps that actual AsyncCallback. Since this is an abstract class, you can limit the scope of onSuccess(), onFailure(), and the newly added method onCall() to be protected. When used as an anonymous inner class, the onCall() method will have access to local variables (as long as they are final) as well as any class variables.

  1. import com.google.gwt.user.client.rpc.AsyncCallback;
  3. public abstract class GwtCallbackWrapper<T> {
  4.   private AsyncCallback<T> asyncCallback = new AsyncCallback<T>() {
  5.     public void onSuccess(T result) {
  6.       GwtCallbackWrapper.this.onSuccess(result);
  7.     }
  8.     public void onFailure(Throwable t) {
  9.       GwtCallbackWrapper.this.onFailure(t);
  10.     }
  11.   };
  13.   protected final AsyncCallback<T> getAsyncCallback() {
  14.     return asyncCallback;
  15.   }
  17.   protected abstract void onSuccess(T t);
  18.   protected abstract void onFailure(Throwable throwable);
  19.   protected abstract void onCall(AsyncCallback<T> callback);
  21.   public final void call() {
  22.     onCall(getAsyncCallback());
  23.   }
  24. }

Now, I can easily add in my aspect like behavior w/out really changing the API to the user (the onFailure() and onSuccess() method names and signatures remain unchanged).

  1. import com.allen_sauer.gwt.log.client.Log;
  2. import com.google.gwt.user.client.rpc.AsyncCallback;
  4. public abstract class GwtCallbackWrapper<T> {
  6.   private long start;
  8.   private AsyncCallback<T> asyncCallback = new AsyncCallback<T>() {
  9.     public void onSuccess(T result) {
  10.       Log.debug("Time : " + (System.currentTimeMillis() - start));
  11.       GwtCallbackWrapper.this.onSuccess(result);
  12.     }
  14.     public void onFailure(Throwable t) {
  15.       Log.debug("Time (failure) : " + (System.currentTimeMillis() - start));
  16.       GwtCallbackWrapper.this.onFailure(t);
  17.     }
  18.   };
  20.   protected final AsyncCallback<T> getAsyncCallback() {
  21.     return asyncCallback;
  22.   }
  24.   protected abstract void onSuccess(T t);
  26.   protected abstract void onFailure(Throwable throwable);
  28.   protected abstract void onCall(AsyncCallback<T> callback);
  30.   public final void call() {
  31.     start = System.currentTimeMillis();
  32.     onCall(getAsyncCallback());
  33.   }
  34. }

When I need to make an asynchronous call, I just use my wrapper class, never using the AsyncCallback directly.

  1. public class MyClass {
  3.   @Inject
  4.   private MyServiceAsync service;
  6.   public void someMethod(final String parameter) {
  7.     new GwtCallbackWrapper<MyData>() {
  8.       public void onCall(AsyncCallback<MyData> callback) {
  9.         service.call(parameter, callback);
  10.       }
  11.       void onSuccess(MyData data) {
  12.         //success code goes here.
  13.       }
  14.       void onFailure(Throwable throwable) {
  15.         // failure code goes here.
  16.       }
  17.     }).call();
  18.   }
  19. }

Don’t forget you will need to make any parameter or local variable of someMethod() to be final.

Now it is easy to add in even more “aspects” to the asynchronous calls. Lets say if you get 500 error back, you want to give the user a retry dialog.


  1. import com.allen_sauer.gwt.log.client.Log;
  2. import com.google.gwt.user.client.rpc.AsyncCallback;
  3. import com.google.gwt.user.client.rpc.StatusCodeException;
  5. public abstract class GwtCallbackWrapper<T> {
  7.   private long start;
  9.   private AsyncCallback<T> asyncCallback = new AsyncCallback<T>() {
  10.     public void onSuccess(T result) {
  11.       Log.debug("Time : " + (System.currentTimeMillis() - start));
  12.       GwtCallbackWrapper.this.onSuccess(result);
  13.     }
  15.     public void onFailure(Throwable t) {
  16.       Log.debug("Time (failure) : " + (System.currentTimeMillis() - start));
  17.       if (t instanceof StatusCodeException) {
  18.         StatusCodeException e = (StatusCodeException) t;
  19.         if (e.getStatusCode() >= 500) {
  20.           new MyDialog<T>(GwtCallbackWrapper.this).center();
  21.         } else {
  22.           GwtCallbackWrapper.this.onFailure(t);
  23.         }
  24.       } else {
  25.         GwtCallbackWrapper.this.onFailure(t);
  26.       }
  27.     }
  28.   };
  30.   protected final AsyncCallback<T> getAsyncCallback() {
  31.     return asyncCallback;
  32.    }
  34.   protected abstract void onSuccess(T t);
  36.   protected abstract void onFailure(Throwable throwable);
  38.   protected abstract void onCall(AsyncCallback<T> callback);
  40.   public final void call() {
  41.     start = System.currentTimeMillis();
  42.     onCall(getAsyncCallback());
  43.   }
  44. }

It can “retry” the service call, by just calling the wrapper.call() method.

  1. public class MyDialog<T> extends DialogBox {
  2.   public MyDialog(final GwtCallbackWrapper<T> wrapper) {
  3.     setText("Server not available, retry?");
  4.     Button ok = new Button("OK");
  5.     ok.addClickHandler(new ClickHandler() {
  6.       public void onClick(ClickEvent event) {
  7.         MyDialog.this.hide();
  8.         wrapper.call();
  9.       }
  10.     });
  11.     Button quit = new Button("Quit");
  12.     quit.addClickHandler(new ClickHandler() {
  13.       public void onClick(ClickEvent event) {
  14.         MyDialog.this.hide();
  15.       }
  16.     });
  17.     HorizontalPanel panel = new HorizontalPanel();
  18.     panel.add(ok);
  19.     panel.add(quit);
  20.     setWidget(panel);
  21.   }
  22. }

By wrapping the actual GWT AsyncCallback and using the wrapping class as your base class to your anonymous classes you create for your callbacks, you have any easy way to control the common code from your GWT client code, at least when it comes to making your service calls.

About the Author

Neil Buesing profile.

Neil Buesing

VP - Streaming Technologies

Neil has more than twenty-five years of Object-Oriented development experience, with twenty years in Java/J2EE application development. He has successfully delivered in the role of an architect and as lead developer.

Over the past 2 years, he has lead up the Real-Time Data Practice at Object Partners. He is a Kafka Architect, Developer, and Advocate presenting at multiple Kafka Summits and Meetups.

One thought on “Improving the GWT Async Callback

  1. Kabal says:

    Thanks. I’ve been searching it for some time.

  2. Paul says:

    Neil, this is a great article. Working through this problem with the example is ideal. Re-jigging the way AsynCallback callbacks are used becomes pretty important with more complex applications. This helped me take a new approach.

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Blog Posts
Using Conftest to Validate Configuration Files
Conftest is a utility within the Open Policy Agent ecosystem that helps simplify writing validation tests against configuration files. In a previous blog post, I wrote about using the Open Policy Agent utility directly to […]
SwiftGen with Image & Color Asset Catalogs
You might remember back in 2015 when iOS 9 was introduced, and we were finally given a way to manage all of our assets in one place with Asset Catalogs. A few years later, support […]
Tracking Original URL Through Authentication
If you read my other post about refreshing AWS tokens, then you probably have a use case for keeping track of the original requested resource while the user goes through authentication so you can route […]
Using Spring Beans in a Kafka Streams ExceptionHandler
There are many things to know before diving into Kafka Streams. If you haven’t already, check out these 5 things as a starting point. Bullet 2 mentions designing for exceptions. Ironically, this seems to be […]