Synchronous RPC Calls in GWT

Jason picture Jason · Sep 8, 2011 · Viewed 10k times · Source

(That title alone should cause people to come out of the woodwork to bash me with clubs, but hear me out).

I have a use case where I need to return a value from a asynchronous call. (I'm using GWT-Platform, but the concepts are the same.) I declared a final JavaScriptObject array, then assigned the value within the AsyncCallback. However, I need to return the value, and the method returns before the AsyncCallback completes. Therefore, I need to block somehow until the AsyncCallback completes. I need the returned value in another method, or I'd just do what I need to in onSuccess().

I've tried loops, Timers, and a few other methods with no luck. Can anyone help?

@Override
public JavaScriptObject doGetWhereAmIMarker(final double lng, final double lat) {

    final JavaScriptObject[] markerArray = new JavaScriptObject[1];  // ugly hack, I know
    dispatch.execute(new GetLocationDescriptionsAction(lng, lat), new AsyncCallback<GetLocationDescriptionsResult>() {
        @Override
        public void onFailure(Throwable caught) {
            caught.printStackTrace();
        }

        @Override
        public void onSuccess(GetLocationDescriptionsResult result) {
            Map<String, Location> resultMap = result.getResult();
            StringBuffer message = new StringBuffer();
            for (String key : resultMap.keySet()) {
                message.append(key).append(": ").append(resultMap.get(key)).append("\n");
            }

            Map tempMap = new HashMap();
            tempMap.put("TITLE","Location Information");
            tempMap.put("LAT", lat);
            tempMap.put("LNG", lng);
            tempMap.put("CONTENTS", message.toString());

            JavaScriptObject marker = GoogleMapUtil.createMarker(tempMap);
            markerArray[0] = marker;
            if (markerArray[0] != null) {
                GWT.log("Marker Array Updated");
            }

        }
    });

    return markerArray[0];
}

UPDATE: As requested, here is the code that calls doGetWhereIAmMarker(). I've tried having a separate native method with the Google Map object (as a JavaScriptObject) as a parameter, but it appears that passing that object between native methods kills the ability to update said object.

public native void initMap(JavaScriptObject mapOptions, JavaScriptObject bounds, JavaScriptObject border, JsArray markerArray, Element e) /*-{

    // create the map and fit it within the given bounds
    map = new $wnd.google.maps.Map(e, mapOptions);
    if (bounds != null) {
        map.fitBounds(bounds);
    }

    // set the polygon for the borders
    if (border != null) {
        border.setMap(map);
    }

    // set up the info windows
    if (markerArray != null && markerArray.length > 0) {
        var infoWindow = new $wnd.google.maps.InfoWindow({
            content:"InfoWindow Content Goes Here"
        });

        for (var i = 0; i < markerArray.length; i++) {
            var marker = markerArray[i];
            marker.setMap(map);
            $wnd.google.maps.event.addListener(marker, 'click', function() {
                infoWindow.setContent(marker.content);
                infoWindow.open(map, this);
            });
        }
    }

    // need to reference the calling class inside the function(), so set a reference to "this"
    var that = this;

   $wnd.whereAmI=function(lng, lat) {
        [email protected]::whereAmI(DD)(lng,lat);
   }

    $wnd.google.maps.event.addListener(map, 'click', function(event) {
        var lat = event.latLng.lat();
        var lng = event.latLng.lng();
        $wnd.whereAmI(lng, lat);
    });

}-*/;

Answer

Alex Gitelman picture Alex Gitelman · Sep 8, 2011

At some point I had to do something similar but eventually I eliminated that code in favor of asynchronous stuff. Therefore, I can't give exact code that you need to use but only few pointers on how to approach it.

  • Firstly, this blog describes how to do synchronous AJAX using javascript.
  • Second, you must provide support for sync calls. The problem is that GWT does not support the parameter that provides synchronous AJAX calls. Most likely is that they don't want to encourage its use. Therefore you would need to use JSNI to add appropriate method to XMLHttpRequest (which you probably would extend) and then to RequestBuilder (also should extend it).
  • Finally, amend your service using extended RequestBuilder. Something like

((ServiceDefTarget)service).setRpcRequestBuilder(requestBuilder);

And in conclusion - from the same blog post (slightly out of context):

Because of the danger of a request getting lost and hanging the browser, synchronous javascript isn't recommended for anything outside of (onbefore)unload event handlers.