AsyncResult — making async in ActionScript suck less

October 09, 2010 at 09:47 PM | ActionScript | View Comments

Let's face it: the tools Adobe provides for dealing with asynchronous operations in ActionScript are basically crap. We've got AsyncToken, which seems pretty good... Until you try and use it yourself, and realize that there's no way to inject your own result. And then we've got the IResponder interface, which also seems pretty good... Except that sometimes your responder gets a ResultEvent and other times it gets the actual result. And then there are the half million events you can listen for (and remember to remove your event listeners for!). And... Well, if you've spent any time in ActionScript, I don't need to say any more.

But I'm not (usually) one to complain without offering a solution.

So let me present: AsyncResult.

In the simplest case - only handling a result / error - it looks something like this:

UserService.loadUser(userID).complete(function(user:User):void {
    // ... do stuff with the User ...
}, function(error:AsyncError):void {
    // ... handle the error ...
});

But that's not what makes AsyncResult cool.

Imagine, for a minute, what that UserService.loadUser function would look like. Maybe it fires off a request to a server which sends back some JSON, which is then loaded into an instance of User then returned. Pretty straightforward... Except it probably takes a small army of event listeners, callbacks and error handlers (which all need to be documented and tested and maintained and...).

This is the kind of situation which AsyncResult was designed for. Check it out:

public function loadUser(userID:String):AsyncResult {
    var token:AsyncToken;
    var result:AsyncResult = new AsyncResult();
    // (HTTP transaction intentionally simplified)
    token = HTTPService.sendGET("http://.../users/" + userID);
    // The AsyncToken is plugged into the Flex AsyncToken
    // through 'getResponder()'
    token.addResponder(result.getResponder());

    // This is where the magic happens:
    result.complete(function(jsonData:String):User {
        var userData:Object = JSON.loads(jsonData);
        var user:User = User.loadFromObject(userData);
        return user;
    });

    return result;
}

Two things to notice about the above code: first, the complete callback accepts JSON data (not an event) and, more importantly, returns an instance of User. Second, no error handling is done.

This is possible because AsyncToken chains all of its handlers. When a result (or error) is received, it is passed to the first handler. If that handler returns a new value (that is, something other than undefined), that new value is passed to the next handler, and so on.

For example, this is what happens if the complete handlers add1 and add2 are used:

>>> function add1(x) { return x + 1};
>>> function add2(x) { return x + 2};
>>> function printx(x) { trace("got:", x); };
>>> r = new AsyncResult();
>>> r.complete(printx);
>>> r.complete(add1);
>>> r.complete(printx);
>>> r.complete(add2);
>>> r.complete(printx);
>>> r.gotResult(0)
got: 0
got: 1
got: 3
>>>

In addition, if any of the handlers return an AsyncResult, the original AsyncResult will wait for the new AsyncResult to complete, then pass the new result to the next handler.

For example, going back to the User example, imagine that we need a (slightly contrived) loadFavoritesForUserID(userID:String) function. It could look something like this:

function loadFavoritesForUserID(userID:String):AsyncResult {
    var result:AsyncResult = UserService.loadUser(userID);
    result.complete(function(user:User):AsyncResult {
        var token:AsyncToken;
        token = = HTTPService.httpGET(user.getFavoritesURL());
        token.addResponder(result.getResponder());
        return result;
    });
    result.complete(function(jsonData:String):Array {
        return JSON.loads(jsonData);
    });
    return result;
}

Well, that's a short introduction to my awesome AsyncResult. If you're interested, check out the code and hit me up on Twitter, @wolever. And if you're not interested, let me know why in the comments.