01.21.07
AJAX continuations in Javascript
Javascript continuations are are intriguing for AJAX (remote scripting) applications. Here I will show you how to do it.
First what is a continuation? A continuation saves the current state of a program and allows the coder to restart it at any time. A good example (though a poorly scalable one) is a login. Once you attempt to click on a link that requires a login, you go to a separate login page, then proceed to the intended page. If you used a continuation on the server side, you could save the current state of the user, then return him to that state as if the login jump had not occurred. This often presents the constraint that the user must return to the same server, a usual no-no for medium-sized startups. It also wastes memory or a database call that should be used for something else.
On the client side, though, the browser and user remain in place. Client-side continuations still use up memory, but here we stick it to the user, not to your poor server. We can use continuations to make AJAX coding easier. The callback is implied. All calls to the server appear to receive data instantly. Then the code can act on the data on the next line.
Though not natively implemented, continuations can be created using Javascript’s own flexibility. It turns out that JS knows what function it is running, can save the call stack through closures, and has access to the source code it is invoking.
This turns out to be a fun experiment. Can we save the entire environment, then pick up where we left off? We do below with a bit of PHP and Javascript. The tail of the function with the continuation is saved. It all appears synchronous, even when it is not.
Thanks to Steve Yen for inspiration on this.
<?php
if($_GET['AJAX'] == 'true') {
if($_GET['function'] == 'date') {
echo "{'date': '".date('h:i:s A')."'}";
}
exit();
}
?>
<html>
<head>
<title>Continuations</title>
</head>
<body>
<form method="GET" action="#Oops" onsubmit="get_remote_date(); return false;">
<input type="submit" value="Get Server Time">
</form>
<div id="changeme">
Text to change.
</div>
<script language="javascript">
/*
* Called by pressing the button.
* The tail of function and context are saved in the
* closure function 'tail_fun'
*
*/
function get_remote_date() {
var tail = get_tail(arguments);
var tail_fun = function(response_text) { eval(tail); }
var response_text = remote_request(tail_fun,
"continuation.php?AJAX=true&function=date");
if(!response_text) {
return;
}
eval("response_obj = "+response_text);
var changeme = document.getElementById("changeme");
changeme.innerHTML = response_obj.date;
}
/*
*
* This accepts the callback function and
* makes the correct request.
* 'handle_success()' then checks whether
* data is ready and calls the callback.
*
*/
function remote_request(callback_fun, url) {
var req = false;
if (window.XMLHttpRequest) {
req = new XMLHttpRequest();
} else {
req = new ActiveXObject("Microsoft.XMLHTTP");
}
if (!req) {
return false;
}
req.onreadystatechange = function() {
handle_success(req, callback_fun);
};
req.open('GET', url, true);
req.send(null);
return req.responseText;
}
/*
*
* handle_success takes the XHR object
* and the callback to be used upon reply.
*
*/
function handle_success(req, callback_fun) {
if (req.readyState == 4) {
if (req.status == 200) {
callback_fun(req.responseText);
} else {
alert('There was a problem with the request.');
}
}
}
/*
*
* get_tail() finds the function name
* and parses the function to find the
* last lines after the return. Evaling
* this function allows the continuation.
*
*/
function get_tail(arguments) {
var source = arguments.callee.toString().
replace(/(.|\r|\n)*return;.*(\n|\r)*.*}/,"");
return "{ " + source;
}
</script>
</body>
</html>