Wednesday, September 26, 2007

Coping with Flex Asynchronous Remote Calls - Part II

In my previous post about using modal dialogs to give the illusion of synchronous remote calls in flex to the user, I showed one way of coping with flex remote calls being asynchronous.

Another way is to fully accept the asynchronism of the remote calls, and start designing your application using callbacks instead.

Imagine the situation, where some code on the client needs to make this (seemingly simple) call:

var result : String = remoteService.doServerSideCalculations(42);

Where remoteService.doServerSideCalculations() is a call to a remote web service. Looks like a no-brainer when coming from the Java world. It is just a method call with a return value. Well, no. Because the remote call is asynchronous, you will not get the return value of the call into result.

The next thing you try when you do not know actionscript3, is to look for some call in the flex api or actionscript3 language to issue a wait, yield or sleep. This could make it possible to let the client wait for the request to return, and then continue operation from there on. But there exist no such calls or functionality!

What to do then?

Design with Callbacks
What you need to do is start designing with callback methods. What I have done is to wrap the remote service in a proxy class, which takes care of calling the remote service and waiting for results. In the event of remote methods which return a value, the proxy will issue a callback to the caller on the proxy, giving the return value of the remote call as a parameter value.

In the above example, I would create a proxy class in as3 like this:

package com.blogspot.techpolesen {
import mx.rpc.soap.WebService;
import mx.rpc.events.ResultEvent;

public class RemoteService {
private var remoteService : WebService = new WebService();

function RemoteService() : void {
remoteService = new WebService();
remoteService.wsdl = "/services/RemoteService?wsdl";
remoteService.loadWSDL();
}

public function doServerSideCalculation(input : int, callback : Function) : void {
// the listener on the result event will call the callback with the return value
var listener : Function = function(event : ResultEvent) : void {
remoteService.removeEventListener(ResultEvent.RESULT, listener);
callback(remoteService.doServerSideCalculation.lastResult);
};

remoteService.addEventListener(ResultEvent.RESULT, listener);

// do the actual call
remoteService.doServerSideCalculation(input);
}
}
}

Here, the RemoteService as3 class wraps the call to the doServerSideCalculation(int) method in an as3 method, which takes a second argument, the callback function. What the as3 implementation does is to add an event listener, that waits for the ResultEvent. When this event occurs, the return value is ready, and the callback can be made, giving the return value back to the caller of the proxy.

Here is an example on how the above proxy class can be used in the application:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import com.blogspot.techpolesen.RemoteService;

private var remoteServiceProxy : RemoteService = new RemoteService();

private function callIt() : void {
lbl.text = "Calling...";
remoteServiceProxy.doServerSideCalculation(42, function(result : int) : void {
lbl.text = "Result: " + result;
});
}
]]>
</mx:Script>

<mx:Button label="Do remote call using callback" click="callIt()"/>
<mx:Label id="lbl"/>
</mx:Application>

And here is a bit of explanation:
  • When the button is clicked, the callIt() method is called
  • The callIt() method invokes doServerSideCalculation on the client-side as3 proxy for the remote service
  • The key is in the parameters to that method. The first parameter is easy, 42, which is simply the parameter to the actual remote call.
  • But the second parameter, callback, is of Function type. It takes a reference to a method. This method is the callback, that will be called when the remote call returns.
  • In the implementation of doServerSideCalculation, an event listener is added on the result event. This listener will be called by flex, when the remote call returns. The listener in turn grabs the return value from the method, and executes a call on the callback method, passing the remote call return value as parameter.
What happens when you click the button, is that callIt calls the doServerSideCalculation, method giving it a callback method. You can view this method as the left-side of the result = doServerSideCalculation(input) call. It simply assigns the result to a label.

You could also combine the above with a modal dialog or a modal dialogs with some progress indication. You would then open the modal dialog in doServerSideCalculation.

Downloading the source
I have zipped up the sources for you. It can be downloaded from here. Ready to be build with maven.

This is a multi-module maven build. There are two directories:
  • client : Contains the flex source and a pom to build it
  • server : Contains the web service and a pom to build it
The war artifact output from the server module have in it the flash output from the client module and a index.html which loads it.

To start the server after a build, you simply jump into the server directory and do a "mvn jetty:run-exploded".

Other Small Flex Tutorials
This was lesson 8 in my series of posts on what I learn about developing filthy rich flash apps using flex2. If you want to read more, the previous lessons can be found here:

4 comments:

David said...

i think this post is excellent, however, i seem to have run into a race condition when calling the same remoteService function back to back on consecutive lines of code in flex.

i would expect to see a different output from each function call in flex, however they are always the same, whether they take the value of the first function call or the second. the web service handles each call differently and returns the right values - yet, there are two problems that occur:

1)i placed print outs on the server at the start of the function call and at the end of the call before the return statement. you would expect to see START FINISH START FINISH, however the server is printing out START START FINISH FINISH (with the correct values in each finish). not sure if this is simply lag, but this appears to happen every time.

2) the outputs in flex are always the same, even though the server outputs clearly indicate they are different

any ideas?

thanks!

Per Olesen said...

Hi David,

Glad you like the post.

I have some ideas for you here:

1)
Are you using the "webserviceClass.operationName.lastResult" construct (as in my example) to get at the result of the call?

Cause if you are, it might not work :-) when calling consecutively on the same webservice. It will containt, well, last result :-)

You can obtain the result from the ResultEvent instead. Like "event.result". I have not tried this out, but I think this is the way to go for you.

2) The "concurrency" property.

The apidocs documents a concurrency property, that controls what flex should do with ongoing calls, if you issue a new call when one is active.

This is multiple by default (which might very well be what you want), but you need to control ordering of events yourself (ResultEvent, FaultEvent) of the calls.

Try getting the result from the event instead.

David said...

Per! Thanks for the help, the problem originated in flex when i declared a single instance of the RemoteService class.

I then used this one instance to make multiple calls to the webservice. However, this introduced a problem when the RemoteService class called the following line of code: remoteService.addEventListener(ResultEvent.RESULT, listener);

Since it looks like only one instance of a class can have a result listener, the solution was to make two instances of the RemoteService class in flex to keep the result listeners separated, and thus the results of the webservice calls separated as well.

Thanks!

Per Olesen said...

Hi again David.

Nice thing you solved it with multiple instances of webservice. The solution I told you about on using event.result was complete rubbish. Sorry.

But there is another and possibly nicer solution, in using tokens on the calls. I show how to do it in this post about how to do remote, concurrent and aynchronous flex web service calls.