Posts Tagged ‘callback’

Asynchronous JavaScript Queue/Sequence

Friday, October 8th, 2010

What are Asynchronous Functions?

An asynchronous function is a function whose completion depends on the actions of another object. The program in which it runs does not wait for the object to return a result, rather, that object “pushes” the result when it’s finished. This push comes through the callback function – a function defined specifically for the purpose of receiving a result, and that object calls the function with arguments it supplies. The signature of the callback function contains these arguments, and uses them in order to complete the procedure.

For example: asynchronous use of XMLHTTP. Upon sending the request to the server, the other scripts keep running while the page continues functioning. The XMLHTTP object has an event named onreadystatechange which is called each time the server returns an answer. This is unlike the synchronous case, where the browser waits for a response from the server. The window ‘freezes’ and only comes back to life when the response is received. This behavior is simply not Internet-correct, and not user-friendly.

Usually when we have an asynchronous function, we’d like another function to run upon its completion. We’ll put the call to the second function in the callback of the first one.

If we have a large number of functions and callback, they start to depend on each other: when A terminates call B, when B terminates call C, and so on. If I pull B out of the sequence, I’ve broken the chain, because C will not be called.

Case of Asynchronous Functions in Sequence

As an example, we’ll look at this sequence:

  1. Performing a semi-transparency effect on an element to be displayed, and once complete:
  2. A button appears in the element, when clicked:
  3. An XMLHTTP request is sent to the server, and when a response is received:
  4. A message is displayed, alerting that the process has finished.

The division into functions is quite simple:

function effectElement() {
	var div=document.getElementById("div");
	var opacity=0;
	var iv=setInterval(function () {
		opacity+=.05;
		div.style.opacity=opacity;
		if (opacity>=1) {
			clearTimeout(iv);
			showButton();
		}
	},50);
}
function showButton() {
	var button=document.getElementById("button");
	button.style.display="";
	button.addEventListener("click",function () {
		requestServer();
	},false);
}
function requestServer() {
	var xh=new XMLHttpRequest();
	xh.open("GET","url.htm",true); // true - async call
	xh.onreadystatechange=function () {
		if (xh.readyState==4) {
			div.innerHTML=xh.responseText;
			finish();
		}
	};
	xh.send(null);
}
function finish() {
	alert("finish");
}
window.addEventListener("load",function () {
	effectElement();
},false);

We see here that we can call the next function within each function’s callback. But what would happen if we were to remove one of the functions from the sequence? We’d have to change the function containing the call to the removed function, to call another function. And what would happen if we wanted to change the order? A mess.

Generic Solution for a Sequence of Asynchronous Functions

I’ve tried to think of a generic solution to the problem, a solution which would let me decide which function comes after which with minimum code and maximum flexibility, and where each function calls the next without being dependent on it.

This is the basic syntax:

var sequence=new Devign.Sequence();
sequence.add(effectElement);
sequence.add(showButton);
sequence.add(requestServer);
sequence.add(finish);
sequence.start();

Within each callback, the current sequence should be advanced by:

currSequence.next();

where currSequence is passed with the function as a single argument.

Simple, isn’t it?

We can play with the order of the functions, add and remove as much as we want.

Devign.Sequence Class

// Devign.Sequence class
if (typeof(Devign)=="undefined") var Devign={};
Devign.Sequence=function () {
	// private fields
	this.list=[]; // a list of functions
	this.index=-1;
	this.aborted=false;
	// public fields
	this.finished=false;
};
// public methods
Devign.Sequence.prototype={
	// adds a new function
	add:function (sequenceFunction) {
		this.list.push(sequenceFunction);
	},
	// starts the sequence from the first function
	// fires 'onStart' if exists
	start:function () {
		this.index=-1;
		this.aborted=false;
		this.next();
		if (typeof(this.onStart)=="function") this.onStart();
	},
	// ends the sequence
	// fires 'onEnd' if exists
	end:function () {
		if (typeof(this.onEnd)=="function") this.onEnd();
		this.finished=true;
	},
	// proceeds the sequence
	// if the sequence has finished calls 'end'
	next:function () {
		// if sequence was aborted - ignore next statements
		if (this.aborted) return;
		this.index++;
		if (this.index==this.list.length) return this.end();
		var currFunction=this.list[this.index];
		// calls the function with the sequence as an argument
		if (currFunction) currFunction(this);
	},
	// aborts the sequence by setting the index to 'not started' and the flag aborted to true
	abort:function () {
		this.index=-1;
		this.aborted=true;
	}
};

Each instance of the class has an array of functions, to which we can add more functions with the method add. Furthermore, there is numerical field named index which tracks the index of the function currently being executed (with -1 meaning the sequence has not yet begun).
For each instance we can also define two events: onStart and onEnd. If defined, they will run at the appropriate time.
Each time we move forward in the sequence, the index is incremented and the next function is called from the array. The function is called with the parameter this which refers to the instance of the class. The reference to the class is sent so that it may be identified from within the function, and advanced with next.

Complete Code

<script type="text/javascript" src="Devign.Sequence.js"></script>
<script type="text/javascript">
var sequence=new Devign.Sequence();
sequence.add(effectElement);
sequence.add(showButton);
sequence.add(requestServer);
sequence.add(finish);
window.addEventListener("load",function () {
    sequence.start();
},false);
function effectElement(currSequence) {
    var div=document.getElementById("div");
    var opacity=0;
    var iv=setInterval(function () {
        opacity+=.05;
        div.style.opacity=opacity;
        if (opacity>=1) {
            clearTimeout(iv);
            currSequence.next();
        }
    },50);
}
function showButton(currSequence) {
    var button=document.getElementById("button");
    button.style.display="";
    button.addEventListener("click",function (e) {
        currSequence.next();
        button.disabled=true;
        button.value="Wait...";
        e.preventDefault();
    },false);
}
function requestServer(currSequence) {
    var div=document.getElementById("div");
    var xh=new XMLHttpRequest();
    xh.open("POST","/ajax_html_echo/",true); // true – async call
    xh.onreadystatechange=function () {
        if (xh.readyState==4) {
            div.innerHTML=xh.responseText;
            currSequence.next();
        }
    };
    xh.send("html=Ajax completed");
}
function finish(currSequence) {
    alert("finish");
    currSequence.next();
}​
</script>
<div id="div" style="opacity:0;height:100px;width:100px;border:1px solid #000;background-color:#32C3D7;">
	<input type="button" id="button" value="Click" style="display:none"/>
</div>

Asynchronous Waiting for JavaScript Procedures

Monday, July 5th, 2010

The Web is asynchronous. Many different types of components make up a system, and each one loads separately. Each component has a different way of loading, and a different way of notifying that it has loaded. Sometimes, it isn’t enough to have the component fully loaded, since affects other components on the page, and we may not always have a way of knowing about it.

Common object types are images, CSS files, JavaScript files, and XMLHTTP Requests. Each has an event to notify us that it has loaded, but having loaded does not mean that the component has begun to affect our page.

For example:

  • Load a CSS file in runtime and appending it to the page does not mean it will immediately affect its elements.
  • Receiving HTML code via an XMLHTTP request and appending it to the page does not mean that it will all be immediately rendered and that its components are accessible.
  • Loading a JavaScript file does not mean all of the objects it contains are ready for use.

For these extreme cases, I’ve written an elementary function which waits for a certain procedure to complete and only then executes further code.

/// $waitUntil
///		waits until a certain function returns true and then executes a code. checks the function periodically
/// parameters
///		check - a function that should return false or true
///		onComplete - a function to execute when the check function returns true
///		delay - time in milliseconds, specifies the time period between each check. default value is 100
///		timeout - time in milliseconds, specifies how long to wait and check the check function before giving up
function $waitUntil(check,onComplete,delay,timeout) {
	// if the check returns true, execute onComplete immediately
	if (check()) {
		onComplete();
		return;
	}
	if (!delay) delay=100;
	var timeoutPointer;
	var intervalPointer=setInterval(function () {
		if (!check()) return; // if check didn't return true, means we need another check in the next interval
		// if the check returned true, means we're done here. clear the interval and the timeout and execute onComplete
		clearInterval(intervalPointer);
		if (timeoutPointer) clearTimeout(timeoutPointer);
		onComplete();
	},delay);
	// if after timeout milliseconds function doesn't return true, abort
	if (timeout) timeoutPointer=setTimeout(function () {
		clearInterval(intervalPointer);
	},timeout);
}

POC

var globalVariable=0;
setTimeout(function () { globalVariable=1; },2000);
$waitUntil(
	function () {
		console.log("checking globalVariable="+globalVariable);
		return globalVariable==1;
	},
	function () {
		alert("done!");
	}
);

In the example, we can see there is a variable named globalVariable initialized to 0, and that is manually set to 1 after 2 seconds. The function $waitUntil takes two functions. One will return true / false according to tests it will carry out, and the other will execute only after the first function returns true.

Examples of more common uses:

ajax("url.htm",function (source) {
	element.innerHTML=source;
	$waitUntil(
		function () { return document.getElementById("some-id-in-source")!=null; },
		function () { /* source is rendered and #some-id-in-source is available */ }
	);
});
loadCss("file.css",function () {
	$waitUntil(
		// element is an element whose height affected by a rule in file.css
		function () { return element.offsetHeight>0; },
		function () { /* css is applied on this element */ }
	);
});
loadJs("file.js",function () {
	$waitUntil(
		function () { return typeof(SomeTypeInFileJs)!="undefined"; },
		function () { /* SomeTypeInFileJs is ready to use */ }
	);
});

The ajax, loadCSS and loadJS functions asynchronously load files and notify that they have loaded. Their implementation can be found in any self-respecting JavaScript library.