...or why they can't work the way I want them to.
I was reading Simon Tatham's brilliant essay on Coroutines in C the other day after hunting around to see if anyone had implemented continuations in JavaScript. This is a jumbled account of how I came to want and fail to make them work (yet).
JavaScript is a wonderful language, it has the expressiveness of Perl with the syntactic clarity of Java. As much as I love Perl, sigils have always bugged me a little and I really really like writing single-line blocks without braces or inverting the order as Perl requires.
A lot of the stuff I've been writing lately fits a basic pattern: object A calls a method on object B and supplies a callback so that when the operation is completed, object B can supply object A with the result. Generally the sort of thing that object B does involves making a request to the server or querying the user for some information.
An example of this would be a modified window.confirm()
method. Assume your favorite browser overloads the function with an additional argument: window.confirm( message, callback )
The function would return immediately, returning control to the calling function and after the user clicks OK or Cancel, the callback function is called with a single value of true
or false
.
function step() {
var message = "Your mouse position has changed. "+
"Click OK to confirm the change or " +
"Cancel to return to the original position.";
window.confirm( message, correct );
}
function correct( result ) {
if( result )
revertMouse();
}
Callbacks work pretty well. Since JavaScript supports lexical closures, it's trivial to create a closure that simply wraps the this
object and a particular method. Our core.js
has two factory methods for doing just that: bind()
and bindEventListener()
. Our example rewritten as a class:
Annoy = new Class( Object, {
step: function() {
var message = "Your mouse position has changed. "+
"Click OK to confirm the change or " +
"Cancel to return to the original position.";
window.confirm( message, this.correct.bind( this ) );
},
correct: function( result ) {
if( result )
revertMouse();
}
} );
This works pretty well, to a point. If you need to set up multiple levels of asynchronous calls, the code degenerates pretty quickly. Callbacks calling other callbacks, creating closures around other closures, generally doing a good job of concealing or at least confusing the actual flow of the program.
This is especially obvious with AJAX. Ideally you don't want to use a blocking request, tying up the UI in JavaScript, holding the browser hostage. Making asychronous requests to the server end up looking a lot like the above example:
ServerList = new Class( Object, {
init: function( id ) {
this.id = id;
this.length = 0;
},
getItems: function( index, length, callback ) {
var _this = this;
AJAX.call( "getItems", [ this.id, index, length ],
function( response ) {
_this.handleResult( response.result, callback );
} );
},
handleResult: function( result, callback ) {
this.length = result.length;
callback( result.items );
}
} );
The ServerList
class is a model, and there's another class that acts as the view. It handles rendering the list, handling user events, etc. There's a producer-consumer relationship between the model and view classes, and a p-c relationship between the model and the server. Since requesting items from the server is an asynchronous operation, the view class must have a callback (and therefore another closure) to accept the results from a call to the getItems()
method. Just one level deeper, and the ratio of algorithm complexity to code complexity has noticably worsened. The logical flow of the program is obscured in a pair of callbacks and closures in two classes.
The view class is completely dependent on the model class to supply it with information. The callback supplied on every request could be replaced with a single call to instantiate the relationship between the two objects:
ServerList = new Class( Object, {
init: function( id ) {
this.id = id;
this.length = 0;
this.observers = [];
},
observe: function( observer ) {
this.observers.add( observer );
},
unobserve: function( observer ) {
this.observers.remove( observer );
},
getItems: function( index, length ) {
var _this = this;
AJAX.call( "getItems", [ this.id, index, length ],
this.handleResponse.bind( this ) );
},
handleResponse: function( response ) {
var result = response.result;
this.length = result.length;
this.observers.forEach( function( observer ) {
if( observer.itemsLoaded )
observer.itemsLoaded( result.items );
} );
}
} );
All an observing object needs to have is a specified method that the producer recognizes for passing items. In this case it's itemsLoaded()
. The ServerList
class could have multiple observers who can all be notified when new items are loaded. They can determine if and what to do with that data. A hypothetical view class:
ServerView = new Class( Object, {
init: function( list ) {
this.list = list;
this.list.observe( this );
this.offset = 0;
this.length = 10;
this.items = [];
this.loaded = false;
},
setOffset: function( offset ) {
this.offset = offset;
this.loaded = false;
this.draw();
},
draw: function() {
if( !this.loaded ) {
this.list.getItems( this.offset, this.length );
return;
}
var html = "";
this.items.forEach( function( item ) {
html += "" + item.index + ": " + item.name + "
";
} );
document.body.innerHTML = html;
},
itemsLoaded: function( items ) {
items.forEach( function( item ) {
var index = item.index - this.offset;
if( item.index < 0 || item.index >= this.length )
continue;
this.items[ index ] = item;
} );
this.loaded = true;
this.draw();
}
} );
Now the complexity has been mostly moved to the view class. Whether the result is preferable to using callbacks is debatable. An object with a lot of observers could result in a lot of noisy traffic, calling methods on observers when they don't care about the result.
After mucking through the first two options, I realized what I really wanted was continuations. I want to be able to call a JavaScript function that will return after some amount of time with a response, returning control back to my function. I wanted to turn the draw()
function into something resembling this:
draw: function() {
var items = this.list.getItems.yield( this.offset, this.length );
...
}
The idea being that the state of draw()
would be saved while getItems()
went on its merry way to get the data. The draw()
method would return immediately at the getItems.yield()
call and only when that completed would the remainder of the function be called.
So I started hacking on it a bit after reading Simon's article on continuations in C. I figured the same thing could be hacked using closures and goto
. All I would need to do is have a function recompiler that modified a method or function, wrapping the contents in a closure and declaring all lexicals outside the closure. Essentially a continuation factory. So my first stab at what a generated function would look like was this:
Before:
function test() {
var count; // predeclaring all lexicals to simplify function recompiler
count = 0;
while( 1 ) {
sleep( 500 );
document.body.appendChild(
document.createTextNode( count + " " ) );
count++;
}
}
After:
function test() {
var count;
var continuation = function( rval ) {
if( !continuation.label ) {
count = 0;
while( 1 ) {
window.setTimeout( continuation, 500 );
continuation.label = 1;
return;
__1:
document.body.appendChild(
document.createTextNode( count + " " ) );
count++;
}
__1: ;
} else {
while( 1 ) {
if( continuation.label == 1 )
break __1;
return;
}
}
}
return continuation();
}
I had been hoping to use either break
or continue
as a substitute for goto
. Alas, when compiled Firefox complains that label __1
is missing. So I'm at a stopping point. There doesn't seem to be a way (in normal JavaScript anyway) to jump into a loop or other lexical block.
Anyone have any ideas?