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.

Permalink + Comments

ifdiff wraps diffs in `#if`s

October 05, 2010 at 10:38 PM | Uncategorized | View Comments

If you've taken an OS/161 based operating systems course[0], you probably ran into one of those fun little eccentricities that makes higher education so enjoyable: the requirement that all code be added in #if blocks.

For example, if the original code looks like this:

int foo = 3;

And I change it to:

int foo = 4;

I'm actually required to submit something that looks like this:

#if !STUFF
int foo = 3;
#else
int foo = 4;
#endif

Well, my friend Dave didn't find this very amusing, so he commissioned me to write a script which would massage a diff produced by $VCS diff, adding all the #ifs required to make the TAs happy :

$ hg diff -r initial:
...
-int foo = 3;
+int foo = 4;
...
$ !! | ifdiff STUFF
...
+#if !STUFF
 int foo = 3;
+#endif
+#if STUFF
+int foo = 4
+#endif
...

The code can be downloaded and forked over at GitHub: ifdiff — http://gist.github.com/612687.

[0]: For the record, CSC369 was one of my favorite courses.

Permalink + Comments

fixpackages makes ActionScript refactoring a little less painful

October 05, 2010 at 01:05 PM | Uncategorized | View Comments

Damnit, Adobe, you do have a way of making simple tasks especially painful. I've written a little Python script — fixpackages — which makes one common task — refactoring ActionScript packages — a little bit less painful.

It walks through a tree of ActionScript source updating the (stupid) package statement so it matches the file's path, then fixing any imports which have obviously been broken.

For example:

$ head -n3 src/new/Foo.as src/main.as
==> src/new/Foo.as <==
package old { // <--- package doesn't match file path
class Foo.as {

==> src/main.as <==
package {
class main {
    import old.Foo; // <--- import statement is broken
$ fixpackages src/
Changing package old --> new in src/new/Foo.as
Fixed 1 imports in src/main.as
$ head -n3 src/new/Foo.as src/main.as
==> src/new/Foo.as <==
package new { // <--- package matches source path
class Foo.as {

==> src/main.as <==
package {
class main {
    import new.Foo; // <--- all is right in the world
$

The code can be downloaded or forked over at GitHub: fixpackages — http://gist.github.com/473832

And, of course, the standard warning: fixpackages will almost undoubtedly trash your source tree, post annoying messages on Twitter and steal your significant other. Don't run it without having your code under source control, clicking "deny" and taking h{im,er} out for a nice dinner.

Permalink + Comments