Although non-mobile-web-developers may not realize, there is one “feature” of Javascript that causes a lot of pain to Javascript every day — although they may deny it, a case of Stockholm Syndrome, me thinks. That pain point is called asynchronous programming.
Here’s the problem.
Javascript in the browser does not support threads. All the rendering of the page and the Javascript logic that has to be executed shares a single thread. That is OK as long as you do simple things. Simple DOM manipulations, input field validation and so on.
But what if you need to do things that could take a while, say, talk to a database, send an HTTP request to the server — what happens then? In typical synchronous style programming such a HTTP request could look as follows:
var content = fetchURL('http://www.yahoo.com');
processContent(content);
However, fetching the URL could take seconds. Due to the unthreaded nature of this environment, the browser could not do anything during those few seconds because the thread is blocked, resulting in a browser freeze. Bad idea. What can we do about this?
AJAX: Asynchronous Javascript and XML
The idea is to, rather than a blocking, we make a call that returns immediately. When the request finishes, a callback function is called. The request itself is performed on a separate thread (yes, a browser does keep a thread pool for asynchronous actions, but it is not directly accessible for the developer). So, what does that look like?
fetchURLAsync('http://www.yahoo.com',
function(content) {
processContent(content);
});
In addition to the regular arguments, an asynchronous function is also passed a callback function, that takes the result of the computation as an argument. The function is called whenever the fetching of the URL has finished, and in the mean time continues executing other code.
Pretty nice solution, right?
It does lead to code that is harder to read, however. In synchronous style programming, we’re used to having code execute from top to bottom. For instance:
var content = fetchURL('http://www.yahoo.com');
processContent(content);
sayHello();
First we fetch the URL, then we process it, then we say hello. Now consider this asynchronous code:
fetchURLAsync('http://www.yahoo.com',
function(content) {
processContent(content);
});
sayHello();
Naively this could be read as (top to bottom): first we fetch the URL, then we process the content and then we say hello. However, this is almost certainly not the order in which these statements will be executed. The likely execution order will be: first the url fetch request is sent off, then we say hello and then we process the content.
It gets worse when you try to do loops with asynchronous calls. Let’s say you would like to fetch URLs contained in some array sequentially (one after the other) and then say hello. In the synchronous style you would write the following:
for(var i = 0; i < ar.length; i++) {
var content = fetchURL(ar[i]);
processContent(content);
}
sayHello()
Now, how do we do this in an asynchronous style? We may try to do this:
for(var i = 0; i < ar.length; i++) {
fetchURL(ar[i], function(content) {
processContent(content);
});
}
sayHello();
But that’s not going to work. First of all, URLs would now be fetched in parallel (which is kind of cool actually, but not what we wanted) and second, the hello’ing happens too early, not after we’re done with the fetching. What we would need to do is something like this:
ar.reverse();
function done() {
sayHello();
}
function fetchOne() {
var url = ar.pop();
fetchURL(ar[i], function(content) {
processContent(content);
if(ar.length === 0) {
done();
} else {
fetchOne();
}
});
}
if(ar.length === 0) {
done();
} else {
fetchOne();
}
Ouch! The idea here is to use the array as a stack. We define a function that processes one URL. The function pops an element off the array, fetches the URL and when done processes it, it checks if the stack is empty, if so, it says hello (done
function), if not, it recursively calls itself to process the next element (the new top) in the array.
Luckily, this type of code can be wrapped in a nicer looking API, which allows you to rewrite this code as:
asyncForEach(ar, function(url, callback) {
fetchURL(ar[i], function(content) {
processContent(content);
callback();
});
}, function() {
sayHello();
});
But still, wouldn’t you prefer the synchronous version?
Enter mobl
Even though mobl compiles down to Javascript, you do not have to do this style of asynchronous programming. Why? Because the compiler is able to automatically transform synchronous style programs into asynchronous style programs. Yes. Automatically, the compiler does exactly what I just did by hand.
Mobl libraries wrap asynchronous Javascript APIs as synchronous APIs. A simple example. How do you sleep in a Javascript program for 1 second. There’s no sleep
function, and even if there would be, it would block the thread and render the browser unusable. If you want something to happen in about 1 second, what you do is you use Javscript’s setTimeout
, right?
setTimeout(function() {
alert('1 second passed!');
}, 1000);
As you can tell, this is another example of an asynchronous call. Here’s what that code looks like in mobl:
sleep(1000);
alert("1 second passed!");
Let’s have a look at the signature of sleep
in the mobl standard library:
external function sleep(ms : Num) : void
This signature tells us two things:
- There is a function named
sleep
that takes a single (numeric) argument and returns nothing (void).
- This function is asynchronous.
Every external function is assumed to be asynchronous unless stated otherwise (using the sync
keyword, e.g. external sync function
). Being asynchronous means that in addition to the arguments specified in the signature, a callback function is provided that is to be called when the function completes.
This signature by itself does not do anything, it just tells the mobl compiler that “there exists a function with this name in this module”. The compiler then assumes there’s a definition for it elsewhere, in this case in native Javascript:
<javascript>
__ns.sleep = function(ms, callback) {
setTimeout(callback, ms);
};
</javascript>
Mobl enables you to include fragments of “native” Javascript in-between <javascript>
tags. __ns
is a reference to the module’s namespace object. If a function, control or screen is defined in a module it is not put in a global scope, but in a module object named after the module. For instance for the mobl
module this object is named mobl
, for mobl::ui::generic
it’s mobl.ui.generic
, a reference to the object can be obtained using the __ns
shortcut.
The implementation of sleep
takes, as predicted, two arguments: the number of milliseconds to wait, and what to do next (a callback function). It basically delegates this call directly to setTimeout
, just switching the argument order.
Alright, so what happens when we call sleep
from mobl? What is the Javascript code generated by the following mobl code?
sleep(1000);
alert("1 second passed!");
Cleaned-up a little bit, the resulting Javascript looks as follows:
mobl.sleep(1000, function() {
mobl.alert("1 second passed!");
});
The transformation performed by the compiler here is called the continuation-passing style transformation, a pretty well-known transformation in academia, although not often applied outside languages like Scheme.
Thanks to the CPS transform, mobl code can be written in a synchronous style, while still maintaining the advantages of asynchronous code, i.e. being non-blocking. One drawback is that writing code that requires concurrent execution — for instance, fetching all the URLs at the same time — is more difficult to express. Mobl does have the async
block for this purpose, which conceptually executes its body “on a different thread of execution”, i.e. the code after it can run in parallel with the code inside it:
async {
sleep(1000);
alert("I'm last");
}
alert("I'm first!");
Excited? Learn more about mobl.
Although non-mobile-web-developers may not realize, there is one \"feature\" of Javascript that causes a lot of pain to Javascript every day -- although they may deny it, a case of (http://en.wikipedia.org/wiki/Stockholm_syndrome), me thinks. That pain point is called __asynchronous programming__.
Here\'s the problem.
Javascript in the browser does not support threads. All the rendering of ...