Chaining API

Page Contents:

Chaining API Overview

Swiz provides a simple, yet powerful and extensible API for chaining actions together. At the heart of this API are the IChain and IChainStep interfaces. Let's look at them here, starting with IChain.


public interface IChain extends IEventDispatcher
{
	function get position():int;
	function set position( value:int ):void;
	
	function get isComplete():Boolean;
	
	function get stopOnError():Boolean;
	function set stopOnError( value:Boolean ):void;
	
	function hasNext():Boolean;
	function stepComplete():void;
	function stepError():void;
	
	function addStep( step:IChainStep ):IChain;
	
	function start():void;
	function doProceed():void;
}


Most of the methods are self explanatory, so we'll only discuss the most important one directly. The doProceed() method is where a chain does its work. It's where EventChain dispatches events, and where CommandChain executes commands. If you clicked either of those links, you may have noticed that both chain types contain very little code other than their doProceed() implementations. This is because they both extend AbstractChain, which implements the entire IChain interface besides doProceed(). It does not actually implement IChain, however, which forces custom chain types to do so and to implement doProceed(). AbstractChain does implement IChainStep though, which means chains can be nested. Also note that chains can be run in either sequence or parallel mode, with constants defined for each in the ChainType class


public interface IChainStep
{
	function get chain():IChain;
	function set chain( value:IChain ):void;
	
	function get isComplete():Boolean;
	
	function complete():void;
	function error():void;
}


IChainStep is the base interface for objects that can be part of a chain. The methods are again pretty self explanatory, but do notice the complete() and error() methods, which correspond to the stepComplete() and stepError() methods of IChain.

The last interface of the Swiz chaining API is IAutonomousChainStep.


public interface IAutonomousChainStep extends IChainStep
{
	function doProceed():void;
}


While chains will often have specific logic for executing their constituent steps, sometimes it is beneficial for a step to control its own execution. This is especially useful for constructing chains with steps of varying types.

Provided implementations

Swiz provides some useful implementations of the chaining API for you. We previously mentioned EventChain, which is exactly what it sounds like. It allows you to string together EventChainStep instances, and also supports IAutonomousChainStep instances. EventChains can be extremely useful for things like application boot strapping and making multiple remote service calls.

CommandChain provides a mechanism for assembling CommandChainStep instances, as well as AsyncCommandChainStep instances (they extend CommandChainStep and implement IResponder) and implementations of IAutonomousChainStep.

Here is an example of a simple EventChain that dispatches a series of events as a single unit:


public function doEventChain() : void
{
	var eventChain : EventChain = new EventChain( dispatcher );
	eventChain.addEventListener( ChainEvent.CHAIN_COMPLETE, handleChainComplete, false, 0, true );
	eventChain.addStep( new EventChainStep( new UserEvent( UserEvent.USER_SCREEN_ACTIVE ) ) );
	eventChain.addStep( new EventChainStep( new UserEvent( UserEvent.USER_PROCESSING_COMPLETE ) ) );
	eventChain.start();
}

[EventHandler( event="UserEvent.USER_SCREEN_ACTIVE" )]
public function logUserScreenActive() : void
{
	// perform logic or dispatch events when the user screen is active
}

[EventHandler( event="UserEvent.USER_PROCESSING_COMPLETE" )]
public function logUserProcessingComplete() : void
{
	// perform logic or dispatch events when the processing finishes
}

public function handleChainComplete( event : Event ) : void
{
	// perform logic or dispatch events when the chain is complete
}


EventChainSteps may optionally specify a dispatcher. If none is specified, the EventChain will assign its dispatcher to the EventChainStep before executing that step.

FunctionChainStep is another useful class that implements IAutonomousChainStep and allows for deferred function invocation.

Finally, note that BaseChainStep provides a base implementation of IChainStep, making your custom implementations that much easier to write.

Custom implementations

It is extremely easy to extend the chaining API to meet your specific needs. As mentioned above, creating a custom chain type essentially boils down to extending AbstractChain, adding "implements IChain" and then implementing the doProceed() method. Your doProceed() method could execute custom chain step types, log or otherwise audit chain progress or perform any other custom behavior you desire. A simple chain that relies on autonomous steps is shown below.


package chains
{
	import org.swizframework.utils.chain.AbstractChain;
	import org.swizframework.utils.chain.IAutonomousChainStep;
	import org.swizframework.utils.chain.IChain;
	
	public class CompositeChain extends AbstractChain implements IChain
	{
		public function doProceed():void
		{
			IAutonomousChainStep( steps[ position ] ).doProceed();
		}
	}
}


Custom chain steps can be made to perform virtually any action. They will need to implement IChainStep, which is most easily done by extending BaseChainStep. If they implement IAutonomousChainStep they can be virtually self contained, and can easily be combined with other chain step types. An example of a custom chain step which sets a property on an object is shown below.


package steps
{
	import org.swizframework.utils.chain.BaseChainStep;
	import org.swizframework.utils.chain.IAutonomousChainStep;
	
	public class PropertySetterStep extends BaseChainStep implements IAutonomousChainStep
	{
		public var target:Object;
		public var props:Object;
		
		public function PropertySetterStep( target:Object, props:Object )
		{
			this.target = target;
			this.props = props;
		}
		
		public function doProceed():void
		{
			for( var prop:String in props )
			{
				target[ prop ] = props[ prop ];
			}
			complete();
		}
	}
}


Automatic Asynchronous Handling in Chains

The Event Chaining API also supports automatic asynchronous handling of chain steps. If an [EventHandler] method is called as part of an event chain, it can return an AsyncToken or IAsynchronousOperation to participate in automatic asynchronous handling. In that case, the chain that is executing will wait until the operation is complete before proceeding to the next chain step. Further, if there are multiple [EventHandler]'s for a given Event, the chain will wait until all associated asynchronous operations are complete before proceeding to the next chain step.

Not all asynchronous Flash or Flex API operations return AsyncToken. Consequently, the internal logic related to asynchronous handling is based upon IAsynchronousOperation. Swiz provides three concrete implementations that adapt typical Flash / Flex asynchronous operations into IAsynchronousOperations.

  1. AsyncTokenOperation - which adapts an AsyncToken.
  2. AsynchronousChainOperation - which adapts an asynchronous IChain.
  3. AsynchronousIOOperation - which adapts Flash or Flex API operations that signal success or failure by dispatching Event.COMPLETE / Event.CANCEL / IOEvent.IO_ERROR / SecurityErrorEvent.SECURITY_ERROR events (ex. FileReference). AsynchronousIOOperation extends AbstractAsynchronousDispatcherOperation, so you can extend this abstract base class to adapt other asynchronous operations that signal their success or failure via Events if necessary.

As noted earlier, [EventHandler] methods can simply return an AsyncToken. The [EventHandler] metadata processor will automatically adapt the AsyncToken via AsyncTokenOperation.


For example, here is an event chain that will automatically wait for a service call to complete before moving on to the next step. First, we'll see the event that will be used for the asynchronous events in the chain. Note that it extends AsynchronousEvent, which is necessary to allow the event to keep track of the chain step it is associated with:


import org.swizframework.utils.async.AsynchronousEvent;
    
/**
 * By extending AsynchronousEvent (or implementing IAsynchronousEvent), the event can keep track
 * of the chain step it is associated with. This allows the event to participate in asynchronous 
 * event chains.
 */ 
public class UserEvent extends AsynchronousEvent
{
    public static const LOAD_USER : String = "UserEvent.LOAD_USER";
    public static const LOAD_USER_IMAGE : String = "UserEvent.LOAD_USER_IMAGE";
    public static const USER_PROCESSING_COMPLETE : String = "UserEvent.USER_PROCESSING_COMPLETE";

    public function UserEvent( type : String )
    {
        super( type, true, false );
    }
}


And then the code that actually builds up the chain and starts the processing:


public function doAsyncChain() : void
{
	var eventChain : EventChain = new EventChain( dispatcher );
	eventChain.addStep( new EventChainStep( new UserEvent( UserEvent.LOAD_USER ) ) );
	eventChain.addStep( new EventChainStep( new UserEvent( UserEvent.USER_PROCESSING_COMPLETE ) ) );
	eventChain.start();
}

[EventHandler( event="UserEvent.LOAD_USER" )]
/**
 * Since this event handler method returns an AsyncToken, Swiz automatically knows that
 * if the method runs as part of an event chain, it should wait until the asyncronous call 
 * completes before proceeding to the next chain step.
 */ 
public function loadUser() : AsyncToken
{
	return serviceHelper.executeServiceCall( service.loadUser(), handleUserLoadResult );
}

private function handleUserLoadResult( event : ResultEvent ) : void
{
	// process the result
}

[EventHandler( event="UserEvent.USER_PROCESSING_COMPLETE " )
public function userProcessingComplete() : void
{
	// perform logic or dispatch events when the processing finishes
}


As another example, a URL request is adapted via AsynchronousIOOperation to allow Swiz to wait for completion before the chain proceeds:


public function doURLRequestChain() : void
{
	var eventChain : EventChain = new EventChain( dispatcher );
	eventChain.addStep( new EventChainStep( new UserEvent( UserEvent.LOAD_USER_IMAGE ) ) );
	eventChain.addStep( new EventChainStep( new UserEvent( UserEvent.USER_PROCESSING_COMPLETE ) ) );
	eventChain.start();
}

[EventHandler( event="UserEvent.LOAD_USER_IMAGE" )]
/**
 * Load an image. Since this event handler method returns an IAsynchronousOperation, 
 * Swiz automatically knows that if the method runs as part of an event chain, it should wait 
 * until the operation completes before proceeding to the next chain step.
 */ 
public function loadUserImage( event : UserEvent ) : IAsynchronousOperation
{
	var request : URLRequest = new URLRequest( targetImagePath );
	var loader : URLLoader = urlRequestHelper.executeURLRequest( request, handleUserImageResult );
	return new AsynchronousIOOperation( loader );
}

private function handleUserImageResult( event : Event ) : void
{
	// process the result
}

[EventHandler( event="UserEvent.USER_PROCESSING_COMPLETE " )
public function userProcessingComplete() : void
{
	// perform logic or dispatch events when the processing finishes
}


An [EventHandler] method can also construct an asynchronous chain and return that chain as an operation (adapting it via AsynchronousChainOperation). In that case, the original EventChainStep will not proceed until the entire child chain is complete.

Go forth and chain stuff!

Hopefully this page has shown you the ease, power and extensibility of the Swiz chaining API. If you have questions or comments about the API or any of the built in implementations, or even just ideas you'd like to discuss, please head over to the mailing list and we'll be happy to help!