My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Monday, June 04, 2012

Working With Queues

Programming with queues is basically what we do in any case: it does not matter if we write code in that way, we simply think in that way.
This means that our logic flow is generally distributed in tasks where "on case X" we apply "logic/procedure Y" .. isn't it?

The Good'ol GOTO

The goto statement has been historically criticized, as well as the switch one, and in both cases is about entry and exit points in a generic workflow.
Nowadays, we can say the GOTO is not needed anymore thanks to functions, where rather than thinking "when this case occurs, goto this instruction" we call the required function in charge of that specific task providing arguments or context as we go.
We may then agree that GOTO is not really a must have while functions are, with all the power and flexibility we might need, and specially in JavaScript.

On Block-Scope

JavaScript has theoretically no block-scope concept, at least until very latest versions of ECMAScript where blocks can be written in the wild.
Blocks are cool for partially independent operations that should not affect at all the external scope/logic but if we think more about this, the usage of inline function expressions has replaced the block-scope concept for a while.
Even better, any function in JS could be considered a sort of equivalent of a block scope, with the advantage that we can re-call the same function as many times as we want, with limits in recursions, avoiding the GOTO and still using block-scopes.

All Together

What if we use as many functions as we need, in order to complete our flow, without compromising the external environment and being able to re-call segments of our flow when something goes wrong?
This can be easily done with a queue system, like the one showed below:


A Few Examples

Here a very basic example on how to use above queue system. There are two sequential things to do, waiting for some truish condition then do something.

Queue([
function (q) {
q.wait(document.body);
},
function onBodyPresent(q) {
document.body.innerHTML = "Hello queue!";
}
]);

The wait() method calls next "block" to execute, as next function, if any, only if the condition passed as argument is true, waiting otherwise "0 ms", if not specified differently, before the same "wait for" block logic is re-executed.
While this example does not show real potentials of queues based programming approach, the next one could do it.

!function () {
var
init = function (q) {
if (!result) {
number = Math.abs(prompt("factorial of:")) || 1;
result = 1;
}
q.next();
},
verify = function (q) {
if (1 < number) {
result *= number--;
q.unshift.apply(
q,
program.slice(
program.indexOf(init),
program.indexOf(verify) + 1
)
);
}
q.next();
},
showResult = function (q) {
alert(result);
q.next();
},
program = Queue([
init, verify, showResult
]).slice(),
result, number
;
}();

Above code is a factorial program: all logic blocks are known in advance and the queue is constantly re-populated until the condition in the middle is satisfied.
While performances are not the best, but generally speaking performances are never a problem when a queue logic is needed, since queues are awesome specially for asynchronous tasks that are rarely good for real-time programming, this factorial program does not use recursion, is quite easier to understand and debug, and it does not blow the RAM usage: functions are recycled as well as the queue which will never grow more than 3 indexes so just wait ... and the result at some point will appear ;-)

Asynchronous Example

As I have said already, queues are great for asynchronous tasks helping with indentation, never too many nested functions, logical workflow, each function does a little but it does it well, readability, functions are named in a semantic way but minifiers will simply shrink them once executed, and even if this does not look that OOP, I bet once we start getting use to this approach things will be easier than ever.

!function () {
// warning: this code is a proof of concept
// it won't work as it is ...
var
// query selector shortcut
$ = function (css) {
return document.querySelector(css);
},
// while user and pass fields are empty ...
login = function (q) {
q.wait(
$("#user").value.trim()
&&
$("#pass").value.trim()
);
},
// once user and pass are not empty anymore
verify = function (q) {
var xhr = new XMLHttpRequest;
xhr.open("post", "verify.php", true);
xhr.send([
"user=" + encodeURIComponent($("#user").value.trim()),
"pass=" + encodeURIComponent($("#pass").value.trim())
].join("&"));
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
q.result = xhr.responseText;
// call next function
q.next();
}
};
},
// verify if the user exists
authorized = function (q) {
if (q.result === "ok") {
q.push(ok);
} else {
// notify the error plus re-queue logic between
// login and authorized included
q.push.apply(q, [error].concat(program.slice(
program.indexOf(login),
program.indexOf(authorized) + 1
)));
}
q.next();
},
// end of the program
ok = function (q) {
alert("Welcome!");
// could go on with the same queue
// passing through a different program
},
// warning for the user
// plus resetting fields
error = function (q) {
alert("user or pass not recognized");
$("#user").value = "";
$("#pass").value = "";
q.next();
},
// clone of the whole program
// reused later to recycle segments
program = Queue([
login, verify, authorized
]).slice()
;
}();


Where Queues Are Used

Well, almost everywhere ... Testing Frameworks are usually based on queues, specially those with asynchronous support for different tests. Same is for JavaScript or CSS loaders, based on queues when it comes to order.
Any sort of stack/Array not representing data, is usually a queue we consume during our logic ... promises are queues too, and same is for events, these are all queues.
Should I explain more? Probably yes, but I'd prefer if you have a look at these examples, use the simple Queue function I have written, and create something awesome that makes the logic of your app cleaner and better organized.
Last, but not least, did I mention that logging a queue gives us instantly a logic workflow of what's going on and accordingly with where it's going on? console.log(q.slice()) anywhere you need and you'll see all named functions you gonna deal with after the one is executing in that moment.

2 comments:

  1. Hello Andrea,

    Two comments.

    First, after playing around with your example, I've noticed a typo (though it helped me understand the code, which I couldn't get completely at first), indeed the 'showResult' function misses a 'q' argument.

    Second, debugging this, it appeared to me that although you claimed there's no recursion involved there seems to be some anyway.
    In the 'next' function of Queue, we can find '!!callback(q)', and to fire the next function, 'q.next()' is used.
    My suggestion would be to setTimeout 0ms the callback function, if any, to avoid a callstack blowup.

    What do you think of that?

    ReplyDelete
  2. can you give me an example of callstack blowup? ... if you q.next() sync I think it should be sync, that's why there's no setTimeout in q.next() logic

    ReplyDelete

Note: Only a member of this blog may post a comment.