Introduction

One of the most prevalent actions in any Flash or Flex application is: Get stuff from the server back-end. Be it calling a remote object, loading a sub-application, module or resource file, all of these actions have one thing in common: They are asynchronous.

The Flex framework and Flash player seem to use different kinds of asynchronous patterns for different kinds of retrieval. There's the responder method, the event based method (all be it with an optional progress reporting mechanism), calls to a singleton that returns an IEventDispatcher instance or some other interface, etc.
AS3Commons-async aims to facilitate a common interface for these different patterns in order to make using and combining them a little easier. The provided interfaces and base classes will give a developer the opportunity to easily write her or his own logic.

Concepts

AS3Commons-async, broadly speaking, has four concepts:

  • Operation: An asynchronous action.
  • Command: An action with deferred execution, can be asynhronous.
  • Service: A collection of related operations.
  • Task: A collection of commands that are executed using a control flow.

AS3Commons-ASync also offers a number of IOperation implementations for both pure actionscript and Flex specific class:
Commons operations section

Operations

An operation represents any kind of asynchronous action and is described by the IOperation interface:

public interface IOperation extends IEventDispatcher {

	function get result():*;

	function get error():*;

	function get timeout():int;

	function set timeout(value:int):void;

	function addCompleteListener(listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void;

	function addErrorListener(listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void;

	function addTimeoutListener(listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void;

	function removeCompleteListener(listener:Function, useCapture:Boolean = false):void;

	function removeErrorListener(listener:Function, useCapture:Boolean = false):void;

	function removeTimeoutListener(listener:Function, useCapture:Boolean = false):void;

}

Long running operations

Operations that take long amounts of time to complete, such as downloading files benefit from being able to notify the user of their progress. Typically a progress bar could display this. For these types of operations there is the IProgressOperation interface, which is a simple extension of IOperation:

public interface IProgressOperation extends IOperation {

 function get progress():uint;

 function get total():uint;

 function addProgressListener(listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void;

 function removeProgressListener(listener:Function, useCapture:Boolean = false):void;
}

Operation queues

In some cases a collection of IOperations need to be run at once, yet each individual OperationEvent.COMPLETE event doesn't need to be listened for. All that is important is that all of the IOperations are completed successfully. For this situation the OperationQueue class can help out. A simple usage of this class might be like this:

var queue:OperationQueue = new OperationQueue();
queue.addCompleteListener(handleQueueComplete);
queue.addOperation(new FirstOperation());
queue.addOperation(new SecondOperation());
queue.addOperation(new ThirdOperation());
queue.addOperation(new FourthOperation());

And that's all, upon adding the operations the queue immediately starts running. So after all four operations have completed the specified handleQueueComplete method will be invoked and the application can go on about its business.

Commands

A command is any kind of action whose execution is deferred. Only after invoking the command's execute() method will the action do its work. The basic ICommand interface is very modest:

public interface ICommand {

 function execute():*;

}

This represents a synchronous command, so the logic that is executed by an implementation will be available immediately after the call to the execute() method, and typically be returned in the execute()'s result. More interesting is the IAsyncCommand interface which is actually just a combination of the ICommand and IOperation interfaces:

public interface IAsyncCommand extends ICommand, IOperation {

}

This interface allows a command to be constructed that will execute an asynchronous action. So contrary to an IOperation instance an IAsyncCommand can be created, but its logic executed at a later moment. Because the command is asynchronous the result property will be null directly after the execute() method has been called, also the result of the execute() method will be either null or undefined. To retrieve the result of the IAsyncCommand, add listeners for the OperationEvent.COMPLETE and OperationEvent.ERROR events.

Composite commands

Just like IOperations its very well possible that a collection of ICommands or IAsyncCommands (or a mixed collection of them) needs to be executed. For this scenario the CompositeCommand class is ideal. It allows a list of ICommands to be executed in parallel or sequence. The CompositeCommand class is an implementation of the ICompositeCommand interface:

public interface ICompositeCommand extends ICommand, IOperation {

	function addCommand(command:ICommand):ICompositeCommand;

	function addOperation(operationClass:Class, ... constructorArgs):ICompositeCommand;

	function get numCommands():uint;

	function get kind():CompositeCommandKind;
}

Note the addOperation() method, a Class (which implements IOperation) can be passed in along with a number of optional arguments. This IOperation will be wrapped in a GenericOperationCommand instance which can will create the operation class in its execute() method.
A simple example of the usage of a CompositeCommand instance would be like this:

var compositeCommand:CompositeCommand = new CompositeCommand(ComposeiteCommandKind.SEQUENCE);
compositeCommand.addCommand(new FirstCommand());
compositeCommand.addCommand(new SecondCommand());
compositeCommand.addCommand(new ThirdCommand());
compositeCommand.addCommand(new FourthCommand());
compositeCommand.addCompleteListener(handleCompositeCommandComplete);
compositeCommand.addErrorListener(handleCompositeCommandError);
compositeCommand.execute();

This will execute the FirstCommand instance, wait for it to complete, execute the SecondCommand instance, wait for it to complete, etc. If all commands complete successfully the specified handleCompositeCommandComplete method will be invoked, if an error occurs the handleCompositeCommandError method is invoked instead.

The failOnFault property determines whether the CompositeCommand instance should stop executing its list of commands in the case of an error. Default this property is set to false. So all commands will be executed no matter what their result is.

GenericOperationCommand

Having an ICommand implementation of each and every IOperation in an application isn't always practical, therefore the GenericOperationCommand might help out a little. The GenericOperationCommand is nothing more than a simple IAsyncCommand wrapper. All that a GenericOperationCommand needs is the Class of an arbitrary IOperation and an optional list of constructor arguments. For example, here is how the LoadURLOperation would be wrapped:

var genericOperationCommand = new GenericOperationCommand(LoadURLOperation, 'http://www.mydomain.com/index.html');
genericOperationCommand.addCompleteHandler(operationCompleteHandler);
genericOperationCommand.execute();

The OperationHandler helper class

The OperationHandler is a helper class that generically handles IOperation events and either routes their result or error data to a specified method or assigns them to a specified property on an object instance.
Below are some examples of its usage.

Assigning an operation result to an instance property

public class MyPresentationModel {

  private var _operationHandler:OperationHandler

  public var products:Array;

  public function MyPresentationModel(){
    _operationHandler = new OperationHandler(this.errorHandler);
  }

  public function getProducts():void {
    var operation:IOperation = serviceImplementation.getProducts();
    _operationHandler.handleOperation(operation,null,this,"products");
  }

  protected function errorHandler(error:):void {
    //implementation omitted
  }
}

Assigning an operation result to an instance property using an extra method

If the data returned from the IOperation needs some extra processing, specify an extra method for it like in this example:

public class MyPresentationModel {

  private var _operationHandler:OperationHandler

  public var products:ArrayCollection;

  public function MyPresentationModel(){
    _operationHandler = new OperationHandler(this.errorHandler);
  }

  public function getProducts():void {
    var operation:IOperation = serviceImplementation.getProducts();
    _operationHandler.handleOperation(operation,convertArray,this,"products");
  }

  protected function convertArray(input:Array):ArrayCollection {
    return new ArrayCollection(input);
  }

  protected function errorHandler(error:):void {
    //implementation omitted
  }
}

Services

In a lot of cases it makes sense to encapsulate a number of operations in one interface. The easiest example would be the CRUD operations for a specific object.
Let's imagine a user service whose interface looks like this:

public interface IUserService {

 function createUser():IOperation;

 function updateUser(user:User):IOperation;

 function deleteUser(user:User):IOperation;

}

Often the implementation for such a service will rely on a RemoteObject (in a Flex application that is). For this situation AS3commons-async offers the base class RemoteObjectService. So the implementation for the IUserService interface could inherit from this:

public class UserService extends RemoteObjectService implements IUserService {

 public function UserService(remoteObject:RemoteObject) {
  super(remoteObject);
 }

 public function createUser():IOperation {
  return call('createUser');
 }

 public function updateUser(user:User):IOperation {
  return call('updateUser', user);
 }

 public function deleteUser(user:User):IOperation {
  return call('deleteUser', user);
 }
}

This service could be injected into a presentation model, a command, a presenter, etc. All depending on the specific patterns used in an application.
Let's look at a command example:

public class CreateUserCommand extends AbstractOperation implements IAsyncCommand {

 private var _userService:IUserService;
 private var _applicationModel:IApplicationModel;

 public function CreateUserCommand(userService:IUserService, applicationModel:IApplicationModel) {
  _userService = userService;
  _applicationModel = applicationModel;
 }

 public function execute():* {
  var operation:IOperation = _userService.createUser();
  operation.addCompleteListener(handleComplete);
  operation.addErrorListener(handleError);
 }

 protected function handleComplete(event:OperationEvent):void {
  _applicationModel.users.addItem(event.result as User);
  dispatchCompleteEvent(event.result);
 }

 protected function handleError(event:OperationEvent):void {
  dispatchErrorEvent(event.error);
 }

}

In this example the command receives a reference to an IUserService and IApplicationModel instance. In this example we assume the IApplicationModel instance has a property called users of type ArrayCollection. Once the IOperation that was received from the service completes it adds the result of the IOperation (which is a User object) to the user collection in the application model.

Tasks

In order to support more complex execution of ICommand collections there is finally the ITask interface. This interface describes an object that not only can execute collections of ICommands both in parallel and in sequence, but also does this with basic control flow functionality.

First let's take a quick look and the ITask interface:

public interface ITask extends ICommand, IOperation {

 function get context():Object;
 function set context(value:Object):void;

 function get parent():ITask;
 function set parent(value:ITask):void;

 function next(item:Object, ...constructorArgs):ITask;

 function and(item:Object, ...constructorArgs):ITask;

 function if_(condition:IConditionProvider=null, ifElseBlock:IIfElseBlock=null):IIfElseBlock;

 function else_():IIfElseBlock;

 function while_(condition:IConditionProvider=null, whileBlock:IWhileBlock=null):IWhileBlock;

 function for_(count:uint, countProvider:ICountProvider=null, forBlock:IForBlock=null):IForBlock;

 function exit():ITask;

 function reset(doHardReset:Boolean = false):ITask;

 function pause(duration:uint, pauseCommand:ICommand=null):ITask;

 function end():ITask;
 
 function break_():ITask;
 
 function continue_():ITask;
}

First thing that is apparent in this interface is that almost each method returns an ITask instance. This actually implies that this is a so-called fluent interface. Meaning that method calls to a single instance of an ITask can be chained. For instance, if we want to use a Task instance to execute a number of commands in parallel we can write this:

var task:Task = new Task().and(new FirstCommand()).and(new SecondCommand()).and(new ThirdCommand()).and(new FourthCommand());
task.addEventListener(TaskEvent.TASK_COMPLETE, handleTaskComplete);
task.execute();

This is basically the same as an ICompositeCommand set to CompositeCommandKind.PARALLEL, so not that interesting. But the nice thing about a Task is the opportunity to mix the different types of execution. So if we first want to execute a number of commands in parallel but after that a few in sequence, we can use the same Task instance:

var task:Task = new Task().and(new FirstCommand()).and(new SecondCommand()).next(new ThirdCommand()).next(new FourthCommand());
task.addEventListener(TaskEvent.TASK_COMPLETE, handleTaskComplete);
task.execute();

This will first execute FirstCommand and SecondCommand in parallel and afterwards execute ThirdCommand and FourthCommand in sequence.
The first argument for the next() and and() methods can be either an ICommand implementation, or a Class that implements the IOperation interface along with an optional list of constructor arguments for the IOperation instantiation. So this can also be a valid invocation:

new Task().and(LoadURLOperation, 'http://www.mydomain.com/index.html');

Now let's have a look at some control flow elements, what if a certain command needs to be executed more than once? We can use a for_() invocation for this:

var task:Task = new Task();

task.for_(10)
 .next(new FirstCommand())
.end();

task.addEventListener(TaskEvent.TASK_COMPLETE, handleTaskComplete);
task.execute();

This will execute the FirstCommand 10 times. Now, it usually isn't possible to know the exact number of iterations at compile-time. Therefore its possible to pass an optional ICountProvider instance to the for_() method. The ICountProvider interface is quite small:

public interface ICountProvider {
	 function getCount():uint;
}

Any implementations of this interface can perform their own kind of logic to determine the exact count, this implementation can also be an IOperation so the logic for retrieving the count may be asynchronous, internally the ForBlock will take care of this.
An ICommand may also be executed conditionally, for this use the if_() method and pass an IConditionProvider to it:

var task:Task = new Task();

task.if_(new MyConditionProvider())
 .next(new FirstCommand())
.end();

task.addEventListener(TaskEvent.TASK_COMPLETE, handleTaskComplete);
task.execute();

Now let's take a look at the IConditionProvider interface:

public interface IConditionProvider {
	 function getResult():Boolean;
}

The result of the getResult() method determines whether the commands inside the if block will be executed or not. Again, an IConditionProvider implementation may also itself be an IOperation, and therefore have its logic be executed in an asynchronous manner.
Of course, the if block may also have an else block:

var task:Task = new Task();

task.if_(new ConditionProvider())
 .next(new FirstCommand())
 .else_()
 .next(new SecondCommand())
.end();

task.addEventListener(TaskEvent.TASK_COMPLETE, handleTaskComplete);
task.execute();

An IConditionProvider can also be used by a while block:

var task:Task = new Task();

task.while_(new MyConditionProvider())
 .next(new FirstCommand())
.end();

task.addEventListener(TaskEvent.TASK_COMPLETE, handleTaskComplete);
task.execute();

In this case the FirstCommand will be executed for as long as the IConditionProvider.getResult() method returns true.

The control flow of for or while block can be controlled using the break_() and continue() methods:

var task:Task = new Task();

task.while_(new MyConditionProvider())
 .if(new MyOtherConditionProvider())
  .next(new FirstCommand())
 .else
  .break_()
 .end()
.end();

task.addEventListener(TaskEvent.TASK_COMPLETE, handleTaskComplete);
task.execute();

In closing there are three last methods that an ITask implementation offers:

  • exit(): stops the execution of the entire task.
  • reset(): restarts the execution of the current task.
  • pause(): pauses the execution of the current task for a specified period in milliseconds.

Common operations

For pure actionscript related operations AS3Commons offers these IOperation implementations:

  • LoadURLOperation
  • LoadURLStreamOperation
  • NetConnectionOperation

In a separate extension .swc AS3Commons offers these Flex specific common IOperation implementations:

  • LoadModuleOperation
  • LoadStyleModuleOperation
  • LoadResourceModuleOperation
  • HTTPServiceOperation
  • RemoteObjectOperation
  • WebServiceOperation