slasp.js: SugarLess Asynchronous Structured Programming
This package is the result of a personal experiment. From that experiment, I
concluded that making a JavaScript software application asynchronous (i.e.
non-blocking) requires someone to "forget" about commonly used synchronous programming
language constructs and replace them by different ones supporting asynchronous
programming.
This package implements a module that provides artificially created asynchronous
equivalents to common synchronous JavaScript programming language constructs.
Most of these implement structured programming concepts.
There are many software abstractions available to make asynchronous programming
more convenient and to cope with the so-called "callback hell". This library is
different compared to others, because it sticks to the bare bones of the
JavaScript language.
Unlike other software abstractions, it does NOT provide any sugar, such as
asynchronous variants of utility functions found in functional programming
languages, encapsulation of activities into objects, and so on.
However, it does provide more features when it comes to expressing things in an
asynchronous world, such as asynchronous if/while/do-while test conditions and
asynchronous object constructors and methods.
Sugar (of course) can still be built on top of this library.
Concepts
The JavaScript language implements a number of programming language concepts.
The following table lists these concepts, how they are used while programming
synchronous applications, and what the alternative should be used to make the
same concept asynchronous:
Concept | Synchronous | Asynchronous |
---|
Function interface | function f(a) { ... } | function f(a, callback) { ... } |
Return statement | return val; | callback(null, val); |
Sequence | a; b; | slasp.sequence([function (cb) { a; cb(); }, function(b) { b; cb(); } ]); |
if-then-else | if c() t() else e(); | slasp.when(c, t, e); |
switch | switch(c()) { case "a": f(); break; } | slasp.circuit(c, function(r, cb) { switch(r) { ... } }); |
Recursion | function fun() { fun(); } | function fun(cb) { setImmediate(function() { fun(cb); }); |
while | while(c()) { s(); } | slasp.whilst(c, s); |
doWhile | do { s(); } while(c()); | slasp.doWhilst(s, c); |
for | for(s(); c(); inc()) { stmt(); } | slasp.from(s, c, inc, stmt); |
for-in | for(var a in arr()) { stmt(); } | slasp.fromEach(arr, function(a, callback) { stmt(callback); }); |
throw | throw err; | callback(err); |
try-catch-finally | try { a(); } catch(err) { e(); } finally { f(); } | slasp.attempt(a, function(err, callback) { funErr(cb); }, f); |
constructor | function Cons(a) { this.a = a; } | function Cons(self, a, callback) { self.a = a; callback(null); } |
new | new Cons(a); | slasp.novel(Cons, a, callback); |
For most of the concepts listed above, a function abstraction is needed to make
an application asynchronous. These abstractions are provided by this library.
Usage
Currently, this library is tested for usage with Node.js and web browsers.
Node.js
Usage on Node.js is straight forward. It can be installed into a working
directory with the NPM package manager by running:
$ npm install slasp
In the code, the module can be imported with:
var slasp = require('slasp');
Browser
For usage in the browser copy lib/slasp.js
into a folder accessible by a web
page. Then add the following script include to the HTML code of that web page:
<script type="text/javascript" src="slasp.js"></script>
An example
Consider the following synchronous JavaScript code fragment that uses an
implementation of the Gregory–Leibniz
formula to approximate pi up to 5 decimal places:
function printOnConsole(value) {
console.log(value);
}
function checkTreshold() {
return (approx.toString().substring(0, 7) != "3.14159");
}
var approx = 0;
var denominator = 1;
var sign = 1;
while(checkTreshold()) {
approx += 4 * sign / denominator;
printOnConsole("Current approximation is: "+approx);
denominator += 2;
sign *= -1;
}
Although the code above seem to do its job, it also takes a bit of time to
complete. In this time window the environment such as a web browser or web server
is blocked, because the event loop cannot process events.
To resolve the blocking issue, JavaScript's while
construct can be replaced by
a slasp.whilst
function invocation. Moreover, we can also make the expressions
and statements asynchronous by generating tick events:
var slasp = require('slasp');
function printOnConsole(value, callback) {
process.nextTick(function() {
console.log(value);
});
}
function checkTreshold(callback) {
process.nextTick(function() {
callback(null, approx.toString().substring(0, 7) != "3.14159");
});
}
var approx = 0;
var denominator = 1;
var sign = 1;
slasp.whilst(checkTreshold, function(callback) {
slasp.sequence([
function(callback) {
approx += 4 * sign / denominator;
callback(null);
},
function(callback) {
printOnConsole("Current approximation is: "+approx, callback);
},
function(callback) {
denominator += 2;
callback(null);
},
function(callback) {
sign *= -1;
callback(null);
}
], callback);
});
The above expression does not block an environment's event loop allowing a
Node.js HTTP server to still handle incoming connections and a browser to still
handle user input.
Overview
The slasp
module implements the following function abstractions:
sequence(stmts, callback)
Runs a number of asynchronous statements sequentially.
when(conditionFun, thenFun, elseFun, callback)
Runs a selection statement that executes a then statment when the conditional
function yields true and an else statement when conditional statement yields
false.
This function is an asynchronous variant of JavaScript's if-then-else
construct.
circuit(conditionFun, caseFun, callback)
Runs a statment in which a switch statement can be embedded so that a
selection of multiple options can be done.
This function is supposed to represent an asynchronous variant of
JavaScript's switch
construct
whilst(conditionFun, statementFun, callback)
Keeps repeating a statement as long as the condition function yields true. The
condition check is done at the beginning of each iteration.
This function represents an asynchronous variant of JavaScript's while
construct.
doWhilst(statementFun, conditionFun, callback)
Keeps repeating a statement as long as the condition function yields true. The
condition check is done at the end of each iteration.
This function represents an asynchronous variant of JavaScript's do-while
construct.
from(startFun, conditionFun, stepFun, statementFun, callback)
Performs a repetition a given number of times.
This function represents an asynchronous variant of JavaScript's for
construct.
fromEach(startFun, statementFun, callback)
Iterates over the keys of an array or object.
This function represents an asynchronous variant of JavaScript's for-in
construct.
attempt(statementFun, captureFun, lastlyFun)
Attempts to execute a statement and executes a capture function if some exception
has been thrown. Finally, it executes a lastly statement regardless of the
outcome.
This function represents an asynchronous variant of JavaScript's
try-catch-finally
construct.
novel(constructorFun, ..., callback)
Constructs a new object from a given constructor function.
Constructor functions have almost the same signature as conventional synchronous
ones. The main difference is that they use self instead of this to refer to the
object that is constructed. The last parameter should be a callback providing the
constructed object as a result, or an error if some exception has been thrown.
This function represents an asynchronous variant of JavaScript's new
operator.
Examples
The test suite that resides in the tests/
folder also serves an example
showcase:
-
In the nodejs
folder testcases can be found showing synchronous JavaScript
programs and their asynchronous counterparts using the function abstractions
described earlier
-
The html
folder contains a simple JavaScript program embedded in a web page
that is not supposed to block the browser.
API documentation
This package includes API documentation, which can be generated with
JSDuck. The Makefile in this package
contains a duck
target to generate it and produces the HTML files in build/
:
$ make duck
License
The contents of this package is available under the MIT license