16
May
2006

Dealing with asynchronous events, part 2

In the previous entry, I talked about some of the reasons why asynchronous programming can be hard to understand. Now, for some approaches for simplifying things.

Using closures

The first piece of the puzzle is closures. Ely Greenfield (one of the Flex architects) showed me a cool way to use closures to hide some of the complexity of argument passing.

In our last episode, we passed arguments from one part of the code to the next via the call object, like so:

public function getAlbumInfo(albumId: int) : void
{
    // Initiate the call.
    myService.request = { type: "album", albumId: albumId };
    var call : Object = myService.send();

    // Save the albumId for use later.
    call.albumId = albumId;

    // Wire up the handler.
    call.handler = getAlbumInfoResult;
}

public function getAlbumInfoResult(event: Event) : void
{
    // Deal with the results of the previous call.
    var artistId: int = event.result.album.artistId;
    myAlbumList.addAlbum(event.call.albumId, event.result.album);

    // Initiate the next call.
    myService.request = { type: "artist", artistId : artistId };
    var call = myService.send();

    // Save the artistId for use later.
    call.artistId = artistId;

    // Wire up the handler.
    call.handler = getArtistInfoResult;
}

public function getArtistInfoResult(event: Event) : void
{
    // Handle the results of the second call.
    myArtistList.addArtist(event.call.artistId, event.result.artist);
}

If you use closures to handle the argument passing, the code looks like this:

public function getAlbumInfo(albumId: int) : void
{
    var call: Object;

    // Initiate the first call.
    myService.request = { type: "album", albumId: albumId };
    call = myService.send();

    call.handler = function (event: Event) 
    {
        // Handle the results of the first call.
        var artistId: int = event.result.album.artistId;

        // Notice we can use albumId here directly.
        myAlbumList.addAlbum(albumId, event.result.album);

        // Initiate the second call.
        myService.request = { type: "artist", artistId: artistId };
        call = myService.send();

        call.handler = function (event: Event)
        {
            // Handle the results of the second call.
            myArtistList.addArtist(artistId, event.result.artist);
        }
    }
}

This is a bit tricky, so let’s walk through it.

  1. myService.request is set.
  2. The first request is sent.
  3. The callback is stored on the call object for the http request.
  4. Eventually, the result handler is called.
  5. We look up the callback on the call object, and call it.
  6. This causes the first closure to be called.
  7. Inside the first closure, I can access the albumId parameter from the outer function, because that’s how closures work.
  8. Inside of there, a second http request is made.
  9. A new callback is stored on a new call object.
  10. Eventually, the result handler is called.
  11. We look up the callback on the call object, and call it.
  12. This causes closure2 to be called.

So what’s so great about this method? Well, in Ely’s words, “if you squint, it almost looks like you’re not doing asynchronous programming.” You don’t have to register asynchronous event handlers, and you don’t have to do anything too hard to pass arguments around, because of the magic of closures.

~

First attempt at cleanup (using Deferreds)

The first thing to clean up is to formalize how the callbacks are registered. Yes, there is code somewhere in the system that calls the handler once the result comes in, but it’s just some code I wrote somewhere, and it’s not managed by a class. Furthermore, it’s not clear what happens if we want to add an additional handler, or if we want to add a callback for when the call fails.

The Twisted network engine for Python has a notion called “deferred” which is a callback handler object.

In Twisted, all asynchronous calls return an object of type “Deferred”. A Deferred is an object that, among other things, has a list of callbacks and “errbacks” which are called when an error occurs.

One of the benefits of the Deferred approach is that it makes the handling of all asynchronous operations work in a consistent way. In order to ensure consistency, you really want this to be baked into the frameowrk. I did the next best thing, which was to extend the framework myself.

In my first attempt, I created an IDeferred interface and a Deferred type. I decided to use the names “result” and “fault” instead of “callback” and “errback”. I added methods to add and remove handlers, just like in Python. I modified the send() method to return an instance of IDeferred instead of the call object. This allows you to use the following:

public function foo() : void
{
    var d : IDeferred = myService.send();
    d.addResultHandler(blah);
    d.addResultHandler(anotherHandler);
    d.addFaultHandler(blah2);
}

This works fine for result handlers that are best expressed as functions, but doesn’t work so well for the closure trick above:

public function ugly() : void
{
    var d : IDeferred = myService.send();
    d.addResultHandler(
        function(event: Event) : void
        {
            // do something
        }
    );
}

Adding setters

To make the syntax cleaner, I added setters to the mix. By creating a setter called “onResult” for the result handlers and “onFault” for the fault handlers, you can now use assignment, which is a more natural syntax for dealing with closures.

public function pretty() : void
{
    var d : IDeferred = myService.send();
    d.onResult = function(event: Event) : void
    {
        // do something
    }
    d.onFault = function(event: Event) : void
    {
        // do something else
    }
}

This operation clears out any existing result handlers and sets the result handler to be the new function. Because ActionScript 3 doesn’t have an overridable += operator, there was no natural way to express “add handler” using a similar notation.

Hiding the concept of deferred

Ideally, people shouldn’t have to know about the Deferred unless they absolutely need to (for example, if you need to pass the deferred from one function to another). One (tricky) way to hide the use of the Deferred is to chain the result of the send without storing the Deferred in a local variable.

public function tricky() : void
{
    myService.send().onResult = function(event: Event) : void
    {
        // do something
    }
}

Of course, there is no way to attach a fault handler with this approach. I ended up defining a property on my version of HTTPService that stored the most recent deferred. I called it “lastCall” which is clear in some ways and confusing in other ways. If you can think of a better name, I’d love to hear it. In any case, here is what the code now looks like:

public function lessTricky() : void
{
    myService.send();
    myService.lastCall.onResult = function(event: Event) : void
    {
        // do something
    }
    myService.lastCall.onFault = function(event: Event) : void
    {
        // do something else
    }
}

Relying on “lastCall” instead of returning a Deferred

Once I added the “lastCall” property, I ended up using this idiom the most, and backed out the change in which I had previously changed the return value of the send() method. This was partly practical and partly philosophical. In practical terms, I found that certain asynchronous methods (such as DataService.commit()) returned typed objects, which meant it was harder for me to hijack them in a subclass. In philosophical terms, I had always felt guilty about subclassing a class and hijacking the meaning of the return value.

The end result

I’ve been using this approach for a while, and it’s been working out fairly well for me. I still have a niggling concern in the back of my head if this infrastructure might just be too fancy for its own good, but for now, I find it a faster and easier way to work. Here’s a stripped down version of some code I’ve been writing recently. Having the flow of control represented inline like this makes it easier for me to understand what’s going on.

public function authenticate() : void
{
    // Show the dialog.
    var loginDialog : LoginDialog = LoginDialog.show();
    
    loginDialog.onResult = function(event: Event) : void
    {
        // If the user was a new user,
        if (loginDialog.isNewUser())
        {
            // Create an account.
            userManager.create(loginDialog.username, loginDialog.password, loginDialog.email);
            userManager.lastCall.onResult = function(event: ResultEvent) : void
            {
                // more logic here.    
            }
            
            userManager.lastCall.onFail = function(event: ResultEvent) : void
            {
                // add failure logic here.
            }
        }
        else
        {
            // Otherwise, authenticate existing account.
            userManager.authenticate(loginDialog.username, loginDialog.password);
            userManager.lastCall.onResult = function(event: ResultEvent) : void
            {
                // more logic here.    
            }

            userManager.lastCall.onFail = function(event: ResultEvent) : void
            {
                // add failure logic here.
            }
        }
    }
}

17 Responses to “Dealing with asynchronous events, part 2”

  1. Brian Riggs

    Great posts, Sho! This is a problem I’ve been struggling with since I started developing with Flex 2, and the app I’m working on now has several different solutions to it (evidence of my evolving thinking).

    One alternative to solving the multiple calls approach is to have a dedicated responder class to handle the result calls. On each call invokation, the client class creates a new instance of the responder class and registers the appropriate event listeners on it. Because multiple successive calls to the same method will generate multiple responder instances, there is no “multiple calls problem” because each responder instance will respond to its own set of events (and be unaware of the others). (For best practices, the responder class should be made internal to the package since clients don’t need to know, and to enable refactoring.)

    That being said, I really like the closure-based solution — it’s far more readable and compact, and it doesn’t require a second class to handle events. If I wasn’t nearing a deadline I’d consider refactoring my code… :-)

  2. hava

    Nice. All this talk of closures reminds me of lambdas and Scheme.

  3. 会上网的猪 » 处理异步事件(第二部分)

    […] 原文地址:http://kuwamoto.org/2006/05/16/dealing-with-asynchronous-events-part-2/ […]

  4. kuwamoto.org » Blog Archive » Asynchronous calls explained

    […] Note that this is a more basic (and common?) case than the stuff I was talking about earlier. For more advanced solutions, see here, here and here. From: XXXX@XXXXXXXXX […]

  5. Saqib

    Hi Sho,
    I am trying to implement the closure method you have described above. I am having problems with call.handler. The problem is, it is not executing this statement. I have tried copy pasting your code but the problem is still there. Any help?

  6. Dealing with asynchronous events in Adobe Flex « Florecista’s Weblog

    […] Dealing with asynchronous events, part 2 […]

  7. veeky学习笔记» Blog 存档 » 处理异步事件,第二部分

    […] 原文地址:http://kuwamoto.org/2006/05/16/dealing-with-asynchronous-events-part-2/ […]

  8. Geeky Derek » Blog Archive » AS3 programming 101 for C/C++ coders

    […] covering the issues of “Dealing with asynchronous events”. So far, he has Part 1, Part 2, and Part 3 available. I strongly recommend reading these articles. They provide a lot of good […]

  9. AS3 programming 101 for C/C++ coders - Geeky Derek

    […] covering the issues of “Dealing with asynchronous events”. So far, he has Part 1, Part 2, and Part 3 available. I strongly recommend reading these articles. They provide a lot of good […]

  10. weight loss 20 pounds

    To consult with the medical treatment is good. Some girls say when he walks like John Wayne in a Western movie
    or has an ex-girlfriend that walks like John Wayne in a
    Western movie then most probably he is well-endowed. Unfortunately for the old
    age, high levels of human growth hormone, also known as simply growth hormone (GH), also
    shy away from old age.

    Review my web site :: weight loss 20 pounds

  11. Mohammad

    My site; web site – Mohammad,

  12. Mariel

    Review my web-site web page (Mariel)

  13. Marsha

    Check out my page … web site – Marsha

  14. Sven

    Review my web page webpage (Sven)

  15. what is a seo

    People who use the recognizable icons only.
    To take a look at the online companies, you can get really busy.

    You can add into your custom web design companies.
    In custom web design do consider a few of those factors in order to define features and then come on to make sure that your site.
    Navigation that’s over-the-top and difficult. We are all very high price tags under
    their belts.

    Feel free to visit my blog post; what is a seo

  16. Shalanda

    As a third generation East Hamptons native who has school bus the most
    dissatisfaction with the aid of detrimental coalescents.
    As you search around online for contractors are few and far
    between. Some of school bus these times is quite expensive.
    That should be left to professionals who are the
    requirements of the most tried methods by which the employee
    and a promissory note promising thereby that he bilked
    several major organizations.

    Also visit my web site – web page (Shalanda)

  17. Brigette

    Maybe it is best if you want, and design, wooden carports,
    unlicensed contractors garden cabins, garden or grounds after tree removal, for good ole experience with a
    face value of the people who are exploiting illegal immigrants.
    Repairing roofs includes repairing leaks, natural stone
    patio, parking lots or concrete footings, concrete cutting tools.

    Have a look at my website; webpage (Brigette)

Leave a Reply