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.
- myService.request is set.
- The first request is sent.
- The callback is stored on the call object for the http request.
- Eventually, the result handler is called.
- We look up the callback on the call object, and call it.
- This causes the first closure to be called.
- Inside the first closure, I can access the albumId parameter from the outer function, because that’s how closures work.
- Inside of there, a second http request is made.
- A new callback is stored on a new call object.
- Eventually, the result handler is called.
- We look up the callback on the call object, and call it.
- 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.
More »