Custom Development

Parallel Asynchronous Calls in GWT

Ben Northrop

The asynchronous nature of GWT makes for more powerful and usable web applications, but as a developer, it can take a little while to get used to. Here's one example: imagine a scenario where some client-side class needs to call two service operations before it can do something. Simple, right? In a synchronous world, it would be:

public void someMethod() {
  Service someSerive = new SomeService();
  String str1 = someService.foo();
  String str2 = someService.bar();
  doSomething(str1, str2);
}

In GWT, things are necessarily a little trickier. Using the standard RPC plumbing, calls from the client to a remote service are handled asynchronously, via GWT's AsyncCallback mechanism. For example:

someService.foo(new AysncCallback() {
    public void onFailure(Throwable caught) {}

    public void onSuccess(final String str1) {
      // do something here;
    }
});

This is relatively straight-forward. If we wanted to make two calls to some service, however, then do something after both have completed, our first instinct might be to just chain the service calls - e.g. when one service completes, call the second:

public void someGwtClientSideMethod() {
  SomeServiceAsync someService = GWT.create(SomeService.class);
  someService.foo(new AsyncCallback<String>() { 

    public void onFailure(Throwable caught) {}

    public void onSuccess(final String str1) {
      service.bar(new AsyncCallback<String>() { 

        public void onFailure(Throwable caught) {}

        public void onSuccess(String str2) {
          doSomething(str1, str2);
        }
      });
    });
  }

While this works, it's not ideal. First, it's really ugly...and this is just for a case with 2 chained async calls - imagine if you had 3 or 4! Second, it's slower than it needs to be, since the calls are made serially rather than in parallel.

One obvious solution is to just combine the two service operations into one (an argument for this here) - only one call is made to the server, and you don't have to bother with the ugly nesting:

public void someGwtClientSideMethod() {
  SomeServiceAsync someService = GWT.create(SomeService.class);
  someService.fooAndBar(new AsyncCallback<FooBarResult>() { 

    public void onFailure(Throwable caught) {}

    public void onSuccess(final FooBarResult fbr) {
      doSomething(fbr.getFooResult(), fbr.getBarResult());
    }
  });
}

This is better, but unfortunately combining service operations like this isn't always possible (or practical). Services may not be under your control...and even if they are, it's a lot of work to aggregate operations, and could result in a pretty complex/messy service if there are many such combinations.

Another possible solution would be to make the calls in parallel, and then only doSomething() when both service calls return. I developed two classes, ParallelCallback and ParentCallback, for this exact purpose. Using these, the code in the client would look like this:

public void someGwtClientSideMethod() {
  SomeServiceAsync someService = GWT.create(SomeService.class);
  ParallelCallback fooCallback = new ParallelCallback();
  ParallelCallback barCallback = new ParallelCallback();
  ParentCallback parent = new ParentCallback(fooCallback, barCallback) {
    public void handleSuccess() {
      doSomething(getCallbackData(0), getCallbackData(1));
    }
  };
  someService.foo(fooCallback);
  someService.bar(barCallback);
}

Without getting too mired in the implementation guts, essentially each service method has its own ParallelCallback, which is registered with a ParentCallback. When the service has completed, the ParallelCallback then informs the parent it's done, and when the parent has heard back from each of is children, it calls its own handleSuccess() method, overridden and implemented by you. Return data from the individual service ParallelCallbacks can be extracted using the getCallbackData() method.

While this solution definitely still has some conceptual load, it eliminates the double-AsyncCallback-nesting, so seems a little simpler (to me!) than the chaining solution above. Also, as mentioned, RPC calls are made in parallel, so the overall latency should be less.

Anyway, the code is available here if you're interested. Let me know what you think, or if you've found another better way to solve this issue.

Note: this issue was documented on StackOverflow, and there are some good insights there as well (like check out gwt-dispatch for one).

Ben Northrop
ABOUT THE AUTHOR

Distinguished Technical Consultant