blog / Asynchronous Processes and RobotLegs

Asynchronous Processes and RobotLegs

Aug 23, 2011

Asynchronous processes are a common feature of ActionScript applications: we often need to initiate some asynchronous process, wait for a response and handle it. We have good language tools and design patterns for solving these sorts issues.

RobotLegs is an MVCS framework that aims to decrease inter-dependency between different parts of code, so that code is more robust, and more reusable. It does this through a combination of dependency injection and the model-view-controller (+services) design pattern. The 'controller' portion of RobotLegs' MVCS implementation is achieved by offering coders a simple way of implementing the Command pattern and binding commands to events that can be called from other areas of the code.

Problematically, commands are stateless and synchronous. They are not wired up to handle asynchronicity under-the-hood. "Async Commands" amend the Command structure to attempt to solve this problem, but they still contain architectural limitations that force us to restrict the way we code to the potential of the framework. Using Commands for asynchronous processes often require us to 'double wire' between different application elements to solve the problem of passing data between classes.

What I am calling Processes are a redesign of the Async Command concept that seek to resolve these issues. They can be used in parallel with Commands to elegantly handle the problems of asynchronous code in RobotLegs.

The Async Pattern

The event model exists explicitly because we often need to wire up code to be triggered at an indeterminate future event. Fundamentally, client-side programming is about state and asynchronicity: events handle the asynchronicity. We handle them with this sort of pattern:

public function init():void
{
  var process:AsyncProcess = new MyAsyncProcess();
  process.addEventListener(AsyncProcessEvent.COMPLETE, onProcessComplete);
  process.init();
}

private function onProcessComplete(event:AsyncProcessEvent):void
{
  var processs:AsyncProcess = event.process;
  process.removeEventListener(AsyncProcessEvent.COMPLETE, onProcessComplete);

  parse(process.data);
}

Signals improves upon this structure by removing the necessity of event classes and offers some added features like automatically handling the removal of listener functions:

public function init():void
{
  var process:AsyncProcess = new MyAsyncProcess();
  process.completed.addOnce(onProcessComplete);
  process.init();
}

private function onProcessComplete(process:AsyncProcess):void
{
  parse(process.data);
}

This pattern is simple but powerful - create an object to tokenise or act-as-delegate-to the process, bind to some generic event/signal defined on the token, then initialise the process. I take the signal implementation to be a substantial improvement to the original.

The Async Pattern across different objects

A more complex case emerges if one class wants to launch a process in another class and retrieve data from its result. Ideally we want to keep the same three-step process.

class Model
{
  public var service:Service;

  public function init():void
  {
    service.completed.addOnce(onServiceComplete);
    service.init();
  }

  private function onServiceComplete(process:AsyncProcess):void
  {
    parse(process.data);
  }
}

interface Service
{
  function get completed:Signal;

  function init():void;
}

Unfortunately, this pattern is now insufficient, because it is possible for two different objects to trigger an asynchronous process from the same service at overlapping times (such that the second one is triggered before the first response).

class InitModelsCommand
{
  public var a:Model;
  public var b:Model;

  public function execute():void
  {
    a.init();
    b.init();
  }
}

In this scenario, if modelA and modelB both call init() in turn, then modelB will receive the data for modelA, then remove its listener before its own data is retrieved (assuming that modelA's data is returned first, otherwise vice-versa).

This problem comes about because the service exposes a single Signal for multiple requests. There are two options to remedy this limitation:

// option A - the calling service tells the response where to go
interface Service
{
  function init(signal:Signal):void;
}

// option B - the service generates a response signal on a per-call basis
interface Service
{
  function init():Signal;
}

There's not a lot between the two solutions, but I prefer Option B because it keeps together the repsonsibilities for creating and managing delegates to the asynchronous process in the same place as the process itself.

The pattern morphs to this:

class Model
{
  public var service:Service;

  public function init():void
  {
    service.init().addOnce(onServiceComplete);
  }

  private function onServiceComplete(process:AsyncProcess):void
  {
    parse(process.data);
  }
}

interface Service
{
  function init():Signal;
}

Here, be aware that if the signal responds within the init() method then it will not be routed to onServiceComplete. This can be handled by defining a signal that once dispatched, will dispatch immediately to any methods that are then added.

Asynchronous Processes in RobotLegs

The point of RobotLegs is to keep as few dependencies between different parts of code as possible. Ideally a model and a service shouldn't reference each other, so RobotLegs best-practises would suggest you separate their interaction into a Command:

class InitModelCommand
{
  [Inject]
  public var model:Model;

  [Inject]
  public var service:Service;

  public function execute():void
  {
    service.completed.addOnce(onResponse);
    service.init();

    commandMap.detain(this) // if we don't do this for an async command everything could explode
  }

  private function onResponse(process:AsyncProcess):void
  {
    commandMap.release(this) // if we don't do this for an async command commands stay in memory

    model.parse(process.data);
  }
}

There several problems with this implementation. Imagine that this command is called as part of an initialisation routine, and once complete, another command should be called:

class Context
{
  public function onStartup():void
  {
    signalCommandMap.mapCommand(InitModel, InitModelCommand);
    signalCommandMap.mapCommand(InitView, InitViewCommand);
  }
}

How should InitViewCommand be called? The only place where the InitModelCommand is known to be complete is in its own onResponse command:

class InitModelCommand
{
  [Inject]
  public var initView:InitView;

  ...

  private function onResponse(process:AsyncProcess):void
  {
    model.parse(process.data);
    initView.dispatch();
  }
}

This is unsatisfactory. Without this, the command is neatly encapsulated and simple to describe and understand. Once this code is added, the command takes on two responsibilities: firstly to initialise the model, and secondarily to kick-off the InitViewCommand.

The stateless command design pattern is ill-suited for asynchronous processes, and the attempt to use them leads to poor code design.

An asynchronous process for RobotLegs

The simple cases at the start of this article are instructive if we try to design a preferred architecture from the ground up. While retaining the power of RobotLegs to remove class dependencies, we want to find a way to use the simple asynchronous process pattern to wire up processes.

These design considerations have led me to what I've tentatively called the robotlegs-async-process-extension. It's very early stages, and as usual I'm sharing the code more to get feedback than as a polished piece of work, but it functions to provide what I think is a superior alternative to Async Commands.

The structure is roughly like this:

class Context
{
  public function onStartup():void
  {
    // a delegate is a new concept...
    processMap.map(InitModelDelegate, InitModelProcess);
    processMap.map(InitViewDelegate, InitViewProcess);
  }
}

class InitModelDelegate extends ProcessDelegate {}

class InitViewDelegate extends ProcessDelegate {}

class InitProcess extends Process
{
  [Inject]
  public var initModel:InitModelDelegate;

  [Inject]
  public var initView:InitViewDelegate;

  public function execute():void
  {
    initModel.execute().addOnce(onModelInited);
  }

  private function onModelInited():void
  {
    initView.execute().addOnce(onViewInited);
  }

  private function onViewInited():void
  {
    complete();
  }
}

Note the features here:

  • Like the SignalCommandMap extension, a different core structure, the ProcessMap is defined that allows Processes to be defined in a way similar to Commands;
  • Rather than binding an event or signal to a Process, we bind a ProcessDelegate. We need a little bit of extra functionality in the ProcessDelegate for the structure to work satisfactorily, but this is not functionally different from the SignalCommandMap extension;
  • When a delegate is executed, it passes back a signal that the calling object binds to in order to know when the process completes (it may also send back data through this callback);
  • Processes can't be duck-typed, because they also need to inherit the functionality from their base class Process. Process exposes a complete() method that is called when the Process completes. If complete() is called inside execute(), then the Process becomes functionally equivalent to a Command.

In fact, this is not quite the library as developed, because I have started using Notices rather than Signals, so I have ported this across to Signals since it has a much broader adoption. Notices are pared down, simple implementations of Signals that allow you to expose very simple interfaces like the SingularNotice interface. It's a preference thing; if anyone would like the original implementation with Notices, I'm happy to publish it.

I hope that this gives you some food for thought with respect to asynchronous processes. I'd love to know what you think about the idea, the implementation, the implied critique of RobotLegs. Let me know!