msgbartop
The SynaTree Development Weblog
msgbarbottom

22 Jan 09 nQ senses browser load to reduce swarming

I have been running into a lot or problems lately with some of my scripts that rely heavily on Ajax that occurs as soon as the page loads.  It seems that during the inital page load, when all of the scripts and stylesheets and images are coming into the browser, running 30-40 XHR requests just isn’t a very good idea.  I’m sure it possible to write perfect code that doesn’t contain any race conditions and is careful enough to avoid all problems, but when things get really messy sometimes you just wish you could slow the code a little bit and make things happen sequentially; or at least prevent them from swarming all at once and bringing the client to a crawl.

Introducing nQ, my super-simple enqueuing script for MooTools.  The concept is very simple:

You throw partial functions at nQ, which adds them to an internal queue.  After 100ms, the function is supposed to execute.  After the timer has elapsed, nQ checks the wallclock to see if more than 100ms have ACTUALLY passed since the item was queued - if so, that means that the browser is having trouble keeping up with all the requests.  nQ slowly backs off, gradually increasing the timing between successive de-queuing calls, until the wallclock starts to show less burden on the browser.  Then nQ starts speeding up again.

There are a couple of cool features in nQ, including an intelligent faculty that allows it to be used in somewhat more complex ways.  For example, if you are using nQ as the destiny function for my Aggregator (elsewhere here on Nibbles), your data collection will be passed to the nQ wrapper rather than the intended recipient, the inner partial function.  To resolve this situation, any arguments passed to nQ after the partial function will be curried into the partial function prior to de-queuing.

I should mention that this bit of code is very experimental, I only just wrote it today although it does seem to work as expected.  nJoy!

// nQ ultra-simple performance-sensitive queueing mechanism.
	// ---------------------------------------------------------
	QT = null;
	Q = [];
	nQ = function(work){
		/*
		 * This first if is to roll in extra arguments that may arrive
		 * at nQ that were really meant for the work function.
		 */
		if(arguments.length-1)
		{
			var w = work;
			var args = Array.prototype.slice.call(arguments).slice(1);
			work = function(){ return w.apply(w, args) };
		}
		if(!Q)
			return work();
		Q.push(work);
		if(!QT)
			dQ.curry($time(), 100).delay(100);

	};

	dQ = function(start,spacer)
	{
		//if(window.console) window.console.log( "Spacer now " + spacer );
		var now = $time();
		if(Q.length)
		{
			var work = Q.shift(); work();
			if(!Q.length)	// if that was the last item in the queue
				return $clear(QT);
			var drift = now - start;
			var high = Math.floor(spacer*1.10);
			var low = Math.floor(spacer*.90);
			if(drift > high)
			{
				spacer = high;
			}
			if(drift <= low)
			{
				spacer = low;
			}
			dQ.curry(now, spacer).delay(spacer);
		}
		else
		{
			return $clear(QT);
		}
	};
	// --------------------------------------------------
	// end nQ stuff

Please remember that you’ll also need my Curry prototype to use nQ.



Leave a Comment