Async APIs and Callback Functions

OK, let’s start digging into asynchronous APIs–starting simple.

Perhaps the most basic and obvious technique for dealing with asynchronous APIs is to deal with the events via callback functions. For example:

class SomeClass {

var fs:FileStream = new FileStream();

function someFunction( file:File ):void {

fs.addEventListener( Event.COMPLETE, onFileStreamComplete );
fs.openAsync( file, FileMode.READ );

}

When the file has been completely read, the function onFileStreamComplete will be "called back" by the FileStream. At that point all the bytes of the file are available to read:

function onFileStreamComplete( event:Event ):void {

var firstInt:int = fs.readInt();
// ... etc. ...

I said we’d start simple, and that’s almost all there is to this approach. However, there is one subtle issue to be aware of. Note in the example above I made the FileStream a member variable of SomeClass. This is convenient because it makes the FileStream easily available in the callback function.

Making the FileStream a member variable is also essential. If the FileStream were instead declared inside someFunction(), it would go out of scope when someFunction() returned and could be garbage collected before the file is read or events are delivered. Many people are surprised by this, but note that the only reference to the FileStream object is the member variable. The FileStream object has references on event listeners, etc. but only references to an object will protect it from collection.

Now, this will almost never happen to you because (a) the garbage collector runs incrementally and it’ll take it a while to collect the FileStream object and (b) most file reads will finish and dispatch a complete event long before then. But it can happen, which of course means it’ll probably only show up when doing big important demos.

While this is a fine technique for simple programs, there are a couple of problems with it for larger endeavors:

  1. The number of callback functions increases quickly as program size goes up. It becomes difficult to keep track of each callback and what it does.
  2. Code for performing a single logical operation (i.e., read the contents of a file) is spread across multiple functions, making it more complex than necessary.

Tune in next time for techniques that help mitigate these problems.