views:

169

answers:

5

Hello

I have a large application that needs to ensure that various items are loaded (at different times, not just at startup) before calling other routines that depend on said loaded items. What i find problematic is how my architecture ends up looking to support this: it is either littered with callbacks (and nested callbacks!), or pre populated with dozens of neat little

private function SaveUser_complete(params:ReturnType):void
{ 
      continueOnWithTheRoutineIWasIn();
}

and so forth. Right now the codebase is only perhaps 2500 lines, but it is going to grow to probably around 10k. I just can't see any other way around this, but it seems so wrong (and laborious). Also, i've looked into pureMVC, Cairngorm, and these methods seem equally tedious,except with another layer of abstraction. Any suggestions?

+1  A: 

Well asynchronous operations always have this affect on code bases, unfortunately there's not really a lot you can do. If your loading operations form some sort of 'Service' then it would be best to make a IService interface, along with the appropriate MVC Style architecture and use data tokens. Briefly:

//In your command or whatever
var service:IService = model.getService();
var asyncToken:Token = service.someAsyncOperation(commandParams);
//some messaging is used here, 'sendMessage' would be 'sendNotification' in PureMVC
var autoCallBack:Function = function(event:TokenEvent):void
{
    sendMessage(workOutMessageNameHere(commandParams), event.token.getResult());
    //tidy up listeners and dispose token here
}
asyncToken.addEventListener(TokenEvent.RESULT, autoCallBack, false, 0, true);

Where I have written the words 'workOutMessageNameHere()' I assume is the part you want to automate, you could either have some sort of huge switch, or a map of commandParams (urls or whatever) to message names, either way best get this info from a model (in the same command):

private function workOutMessageNameHere(commandParams):String
{
    var model:CallbackModel  = frameworkMethodOfRetrivingModels();
    return model.getMessageNameForAsyncCommand(commandParams);
}

This should hopefully just leave you with calling the command 'callService' or however you are triggering it, you can configure the callbackMap / switch in code or possibly via parsed XML. Hope this gets you started, and as I've just realized, is relevant?

EDIT: Hi, just had another read through of the problem you are trying to solve, and I think you are describing a series of finite states, i.e. a state machine. It seems as if roughly your sequences are FunctionState -> LoadingState -> ResultState. This might be a better general approach to managing loads of little async 'chains'.

A: 

Agreeing with enzuguri. You'll need lots of callbacks no matter what, but if you can define a single interface for all of them and shove the code into controller classes or a service manager and have it all in one place, it won't become overwhelming.

JMHNilbog
A: 

I know what you are going through. Unfortunately I have never seen a good solution. Basically asynchronous code just kind of ends up this way.

One solution algorithm:

static var resourcesNeededAreLoaded:Boolean = false;
static var shouldDoItOnLoad:Boolean = false;

function doSomething()
{
    if(resourcesNeededAreLoaded)
    {
        actuallyDoIt();
    }
    else
    {
        shouldDoItOnLoad = true;
        loadNeededResource();
    }
}

function loadNeededResource()
{
     startLoadOfResource(callBackWhenResourceLoaded);
}

function callBackWhenResourceLoaded()
{
    resourcesNeededAreLoaded = true;
    if(shouldDoItOnLoad)
    {
        doSomething();
    }
}

This kind of pattern allows you to do lazy loading, but you can also force a load when necessary. This general pattern can be abstracted and it tends to work alright. Note: an important part is calling doSomething() from the load callback and not actuallyDoIt() for reasons which will be obvious if you don't want your code to become out-of-sync.

How you abstract the above pattern depends on your specific use case. You could have a single class that manages all resource loading and acquisition and uses a map to manage what is loaded and what isn't and allows the caller to set a callback if the resource isn't available. e.g.

public class ResourceManager
{
    private var isResourceLoaded:Object = {};
    private var callbackOnLoad:Object = {};
    private var resources:Object = {};

    public function getResource(resourceId:String, callBack:Function):void
    {
        if(isResourceLoaded[resourceId])
        {
            callback(resources[resourceId]);
        }
        else
        {
            callbackOnLoad[resourceId] = callBack;
            loadResource(resourceId);
        }
    }

    // ... snip the rest since you can work it out ...
}

I would probably use events and not callbacks but that is up to you. Sometimes a central class managing all resources isn't possible in which case you might want to pass a loading proxy to an object that is capable of managing the algorithm.

public class NeedsToLoad
{
    public var asyncLoader:AsyncLoaderClass;

    public function doSomething():void
    {
        asyncLoader.execute(resourceId, actuallyDoIt);
    }

    public function actuallyDoIt ():void { }
}

public class AsyncLoaderClass
{
    /* vars like original algorithm */

    public function execute(resourceId:String, callback:Function):void
    {
        if(isResourceLoaded)
        {
            callback();
        }
        else
        {
            loadResource(resourceId);
        }
    }

    /* implements the rest of the original algorithm */
}

Again, it isn't hard to change the above from working with callbacks to events (which I would prefer in practise but it is harder to write short example code for that).

It is important to see how the above two abstract approaches merely encapsulate the original algorithm. That way you can tailor an approach that suites your needs.

The main determinants in your final abstraction will depend on:

  • Who knows the state of resources ... the calling context or the service abstraction?
  • Do you need a central place to acquire resources from ... and the hassle of making this central place available all throughout your program (ugh ... Singletons)
  • How complicated really is the loading necessities of your program? (e.g. it is possible to write this abstraction in such a way that a function will not be executed until a list of resources are available).
James Fassett
A: 

In one of my project, I build custom loader which was basically wrapper class. I was sending it Array of elements to load and wait for either complete or failed event(further I modified it and added priority also). So I didn't have to add so many handlers for all resources.

You just need to monitor which all resources has been downloaded and when all resources complete, dispatch a custom event-resourceDownloaded or else resourcesFailed.

You can also put a flag with every resource saying it is necessary or compulsory or not, If not compulsory, don't throw failed event on failing of that resource and continue monitoring other resources!

Now with priority, you can have bunch of file which you want to display first, display and continue loading other resources in background.

You can do this same and believe me you'll enjoy using it!!

Bhavesh.Bagadiya
A: 

You can check the Masapi framework to see if it fulfills your needs.

You can also investigate the source code to learn how they approached the problem.

http://code.google.com/p/masapi/

It's well written and maintained. I used it successfully in a desktop RSS client I developed with Air.

It worked very well assuming you pay attention to the overhead while loading too many resources in parallel.

Peiniau