Best way to implement 1:1 asynchronous callbacks/events in ActionScript 3 / Flex / AIR

actionscript-3airapache-flexasynchronous

I've been utilizing the command pattern in my Flex projects, with asynchronous callback routes required between:

  • whoever instantiated a given command object and the command object,
  • the command object and the "data access" object (i.e. someone who handles the remote procedure calls over the network to the servers) that the command object calls.

Each of these two callback routes has to be able to be a one-to-one relationship. This is due to the fact that I might have several instances of a given command class running the exact same job at the same time but with slightly different parameters, and I don't want their callbacks getting mixed up. Using events, the default way of handling asynchronicity in AS3, is thus pretty much out since they're inherently based on one-to-many relationships.

Currently I have done this using callback function references with specific kinds of signatures, but I was wondering if someone knew of a better (or an alternative) way?

Here's an example to illustrate my current method:

  • I might have a view object that spawns a DeleteObjectCommand instance due to some user action, passing references to two of its own private member functions (one for success, one for failure: let's say "deleteObjectSuccessHandler()" and "deleteObjectFailureHandler()" in this example) as callback function references to the command class's constructor.
  • Then the command object would repeat this pattern with its connection to the "data access" object.
  • When the RPC over the network has successfully been completed (or has failed), the appropriate callback functions are called, first by the "data access" object and then the command object, so that finally the view object that instantiated the operation in the first place gets notified by having its deleteObjectSuccessHandler() or deleteObjectFailureHandler() called.

Best Solution

I'll try one more idea:

Have your Data Access Object return their own AsyncTokens (or some other objects that encapsulate a pending call), instead of the AsyncToken that comes from the RPC call. So, in the DAO it would look something like this (this is very sketchy code):

public function deleteThing( id : String ) : DeferredResponse {
    var deferredResponse : DeferredResponse = new DeferredResponse();

    var asyncToken : AsyncToken = theRemoteObject.deleteThing(id);

    var result : Function = function( o : Object ) : void {
        deferredResponse.notifyResultListeners(o);
    }

    var fault : Function = function( o : Object ) : void {
        deferredResponse.notifyFaultListeners(o);
    }

    asyncToken.addResponder(new ClosureResponder(result, fault));

    return localAsyncToken;
}

The DeferredResponse and ClosureResponder classes don't exist, of course. Instead of inventing your own you could use AsyncToken instead of DeferredResponse, but the public version of AsyncToken doesn't seem to have any way of triggering the responders, so you would probably have to subclass it anyway. ClosureResponder is just an implementation of IResponder that can call a function on success or failure.

Anyway, the way the code above does it's business is that it calls an RPC service, creates an object encapsulating the pending call, returns that object, and then when the RPC returns, one of the closures result or fault gets called, and since they still have references to the scope as it was when the RPC call was made, they can trigger the methods on the pending call/deferred response.

In the command it would look something like this:

public function execute( ) : void {
    var deferredResponse : DeferredResponse = dao.deleteThing("3");

    deferredResponse.addEventListener(ResultEvent.RESULT, onResult);
    deferredResponse.addEventListener(FaultEvent.FAULT,   onFault);
}

or, you could repeat the pattern, having the execute method return a deferred response of its own that would get triggered when the deferred response that the command gets from the DAO is triggered.

But. I don't think this is particularly pretty. You could probably do something nicer, less complex and less entangled by using one of the many application frameworks that exist to solve more or less exactly this kind of problem. My suggestion would be Mate.

Related Question