An Asynchronous Cookbook (Part Two)

In the previous post, I outlined a few different solutions to the problem of how to structure your asynchronous ActionScript code. The concept of an asynchronous method chain, in which a series of methods that make asynchronous calls to external services are executed sequentially, is an important one for Flex developers who want their apps to pull in data from multiple web services. NoteTag is a prime example: it publishes notes to Blogger and/or TypePad, and stores references to notes and tasks on del.icio.us.

But suppose that one method in the asynchronous chain needs some piece of data generated by an earlier method in the chain. How can you make state available to more than one method in the chain?

Problem #4: One or more methods in an asynchronous method chain need access to data generated somewhere else in the chain. For example, imagine that our getFirstFeed method needs to return the first feed in an Atom service document and the Atom service document.

Solution #4a: Stash the data onto the call object and pass it down the chain (per Sho’s suggestion). This approach works fine in the simple case, but suppose you have a really long chain of methods – you might end up passing data through a bunch of methods that don’t need it, just so that the last method can use the data.

Solution #4b: Stash the data on the enclosing object as a private data member. This is perhaps the simplest solution, as it makes the data available to any method in the chain. But if the client method is called a second time before it has finished its first series of asynchronous calls, then the stashed data could be overwritten! What we really want is a way to ensure that any state we generate during the execution of the asynchronous method chain is scoped on a per-method-invocation basis…

Solution #4c: Achieve per-method-invocation variable-scoping by putting the logic for the chained calls into an internally-scoped delegate class which is instantiated when the client method is called. Because a new delegate is instantiated each time the method is called, the data can be stored on the delegate without any risk of its being overwritten. This approach can be done by convention (by forcing your clients to use a factory class that creates a new object for each operation) or explicitly (by having the method literally create a delegate object, as below).

public class FeedRetriever extends EventDispatcher
{
private var protocol:AtomPublishingProtocol; // initialized elsewhere

// Dispatches a GetFeedEvent after retrieving the first feed listed
// in the service document.
public function getFirstFeed(serviceURI:URI):void
{
var delegate:FeedRetrieverDelegate =
new FeedRetrieverDelegate(this,protocol);
delegate.getFirstFeed(serviceURI);
}
}

// Internally-scoped delegate which is instantiated on every call to
// FeedRetriever.getFirstFeed (and which can therefore safely store state).
internal class FeedRetrieverDelegate
{
private var target:IEventDispatcher;
private var protocol:AtomPublishingProtocol;

// State that needs to be tracked (the service document).
private var service:IService;

public function FeedRetrieverDelegate(target:IEventDispatcher,
protocol:AtomPublishingProtocol):void
{
this.target = target;
this.protocol = protocol;
}

public function getFirstFeed(serviceURI:URI):void
{
// Use a utility class to handle registration and unregistration
// of event listeners.
var responder:AsyncResponder = new AsyncResponder();
responder.setNextEventHandler(protocol,
onGetService, // result handler
“getService”, // result event
onFault,      // fault handler
“atomFault”); // fault event

// Make the asynchronous call to retrieve the service document.
protocol.getService(serviceURI);
}

private function onGetService(event:GetServiceEvent):void
{
// Store the service document since the next method needs
// access to it.
this.service = event.service;

// Get the collection that represents the first feed.
var collection:ICollection = service.workspaces[0].collections[0];

// Use a utility class to handle registration and unregistration
// of event listeners.
var responder:AsyncResponder = new AsyncResponder();
responder.setNextEventHandler(protocol,
onGetFeed,    // result handler
“getFeed”);   // result event
onFault,      // fault handler
“atomFault”); // fault event

// Make the asynchronous call to retrieve the first feed.
protocol.getFeed(collection);
}

private function onGetFeed(event:GetFeedEvent):void
{
// Dispatch a result event to the caller, using
// the passed-in target object.
var newEvent:GetFeedEvent = new GetFeedEvent();
newEvent.feed = event.feed;
newEvent.service = this.service;
target.dispatchEvent(newEvent);
}

private function onFault(event:AtomFaultEvent):void
{
// Do fault processing...
}

// ...
}

But it’s not always ideal from an object-orientation perspective to separate this logic into two classes. Is there a way that we can attain per-method-invocation variable-scoping without using multiple classes? Our final solution accomplishes just that.

Solution #4d: Put your responder code in one or more closures. (A closure is a function that exists within the body of another function and which when executed has its own set of local variables.) Within a closure, you can access variables in the outer scope – a nice benefit. The drawback? Because the closure needs to be declared prior to the asynchronous method call, the flow of control within the code can be a bit confusing.

public class FeedRetriever extends EventDispatcher
{
private var protocol:AtomPublishingProtocol; // initialized elsewhere

// Dispatches a GetFeedEvent that contains the first feed and
// the service document.
public function getFirstFeed(serviceURI:URI):void
{
// #1: The flow of control starts here...

// Declare a function closure that will be called later.
var onGetService:Function = function(event1:GetServiceEvent):void
{
// #3: After protocol.getService completes, control jumps
// to here...

// We set the service document into a local variable,
// which is accessible even from within the subsequent
// closure.
var serviceLocalVar:IService = event1.service;

// Get the first feed in the service document.
var collection:ICollection = serviceLocalVar.workspaces[0].collections[0];

// Declare another function closure to be called later.
var onGetFeed:Function = function(event2:GetFeedEvent):void
{
// #5: After protocol.getFeed completes, control
// jumps to here, and finishes by dispatching
// the client event.

// Dispatch a result event to the caller.
var newEvent:GetFeedEvent = new GetFeedEvent();
newEvent.feed = event2.feed

// Notice how we can access serviceLocalVar -
// this is one nice benefit of closures.
newEvent.service = serviceLocalVar;
dispatchEvent(newEvent);
}

// #4: ... and continues down to here...

// Use a utility class to handle registration and
// unregistration of event listeners.
var responder2:AsyncResponder = new AsyncResponder();
responder2.setNextEventHandler(protocol,
onGetFeed, // result handler
GetFeedEvent.GET_FEED, // result event
onFault, // fault handler
AtomFaultEvent.ATOM_FAULT); // fault event

// Make the asynchronous call to retrieve the first feed.
protocol.getFeed(collection);
}

// #2: ... and continues down to here...

// Use a utility class to handle registration and unregistration
// of event listeners.
var responder1:AsyncResponder = new AsyncResponder();
responder1.setNextEventHandler(protocol,
onGetService, // result handler
“getService”, // result event
onFault,      // fault handler
“atomFault”); // fault event

// Make the asynchronous call.
protocol.getService();
}

private function onFault(event:AtomFaultEvent):void
{
// Do fault processing...
}
}

Following the flow of control in the previous example is a bit like watching a yoyo in action. Sho has some ideas
on how to improve the readability of closure-heavy code.

Summary: When it comes to implementing asynchronous methods in ActionScript, there are a number of approaches that you can take. In many cases you’ll be able to stick with the simplest approach (create an event listener to respond to the event), but there are certain situations where the more complicated approaches are called for. For each such situation, ask yourself the following questions:

  • Do I need to process the result of an asynchronous call?
  • Will the object that makes the asynchronous call be used again after I’m done with it?
  • Do I need to make multiple sequential asynchronous calls?
  • In the chain of sequential asynchronous calls, does a method need access to data that is generated by a different method in the chain?

If the answer to any of these questions is “yes”, then consider using one or more of the following:

  • A helper class that can manage event registration (a la AsyncResponder)
  • Stateful objects
  • Delegates that achieve per-method-invocation variable scoping
  • Closures