.eternal
ad infinitum
The important thing is not to stop questioning. Curiosity has its own reason for existing. One cannot help but be in awe when he contemplates the mysteries of eternity, of life, of the marvelous structure of reality. It is enough if one tries merely to comprehend a little of this mystery every day. Never lose a holy curiosity.
![]()
Long running JavaScript tasks
There was a recent post on Ajaxian concerning a pattern for running CPU intensive JavaScript tasks by Julien Lecomte. The technique is not new (which was not his point in the first place) and basically involves keeping some sort of status information to determine if you need to continue or have completed the task, and then using setTimeout to call your task again. You do this so that you don’t lock the browser up because JavaScript runs on the same thread as the UI (except in Opera). I had some thoughts about how one could “class”-ify using the Prototype framework (although the same thing could be done easily enough using any framework, or no framework for that matter).
To start, let’s have a look at a very simple solution (framework-free):
function task(fn) {
if(fn()) {
setTimeout(function() { task(fn); }, 100);
}
}
var myCounter = 0;
function myFunction() {
document.getElementById("log").innerHTML += myCounter + "<br />";
myCounter++;
return myCounter < 10;
}
window.onload = function() {
task(myFunction)
};
The function task calls the function fn that is passed in as a parameter. If fn returns true (or any “truthy” result), it will schedule itself again after 100ms. In this case, fn is the function myFunction which essentially outputs counter myCounter, increments it, and then returns true if it’s still less than 10.
Okay, I said simple. The problem with this is that the myCounter variable is global which we’d like to avoid. The solution, of course, is to use closures. If we add some anonymous functions too, we end up with (using the same task function from above):
window.onload = function() {
(function() {
var i = 0;
task(function() {
document.getElementById("log").innerHTML += i + "<br />";
i++;
return i < 10;
});
})();
};
This version does exactly the same thing without polluting the global namespace. The anonymous function closure means that our status information, this time the variable i, is available to the task function but not elsewhere.
Enough of that. What about if we want to start and stop tasks? Or get progress or completion notifications? Prototype has class functions which we can use to create a nice encapsulated task runner class.
var Task = Class.create();
Task.prototype = {
initialize: function(callback, options) {
this.callback = callback;
this.options = Object.extend({
autoStart: true,
timeout: 100,
onProgress: null,
onComplete: null
}, options || {});
if(this.options.autoStart) {
this.start();
}
},
start: function() {
if(this.running) return;
this.running = true;
this.run();
},
stop: function() {
clearTimeout(this.timeout);
this.running = false;
},
toggle: function() {
if(this.running) {
this.stop();
} else {
this.start();
}
},
run: function() {
if(this.callback()) {
if(this.options.onProgress) {
this.options.onProgress();
}
this.timeout = setTimeout(this.run.bind(this), this.options.timeout);
} else {
this.running = false;
if(this.options.onComplete) {
this.options.onComplete();
}
}
}
};
The Task class can be used in the same way as the task function. For example, using the first method:
Event.observe(window, "load", function() {
new Task(myFunction);
});
Will produce the same results. But if we’re going to class-ify things, let’s go all the way and encapsulate the tasks too.
var RunMe = Class.create();
RunMe.prototype = {
initialize: function(id, max) {
this.id = id;
this.max = max;
this.counter = 0;
},
run: function() {
this.counter++;
return this.counter < this.max;
},
progress: function() {
new Insertion.Top("log", this.id + " - " + this.counter + " out of " + this.max + "<br />");
},
complete: function() {
new Insertion.Top("log", this.id + " - complete<br />");
}
};
Event.observe(window, "load", function() {
var runMe1 = new RunMe("RunMe 1", 20);
var runMe2 = new RunMe("RunMe 2", 10);
var runMe3 = new RunMe("RunMe 3", 100);
var runMeTask1 = new Task(runMe1.run.bind(runMe1), {
autoStart: false,
timeout: 500,
onProgress: runMe1.progress.bind(runMe1),
onComplete: runMe1.complete.bind(runMe1)
});
var runMeTask2 = new Task(runMe2.run.bind(runMe2), {
autoStart: false,
timeout: 1000,
onProgress: runMe2.progress.bind(runMe2),
onComplete: runMe2.complete.bind(runMe2)
});
var runMeTask3 = new Task(runMe3.run.bind(runMe3), {
autoStart: true,
timeout: 250,
onProgress: runMe1.progress.bind(runMe3),
onComplete: runMe1.complete.bind(runMe3)
});
Event.observe("runMe1", "click", runMeTask1.toggle.bind(runMeTask1));
Event.observe("runMe2", "click", runMeTask2.toggle.bind(runMeTask2));
Event.observe("runMe3", "click", runMeTask3.toggle.bind(runMeTask3));
});
Now the tasks can be started and stopped, and the progress and complete notifications are separated from the task execution itself.
The example above can be seen here (in all it’s barely-styled, quickly hacked, glory).

The important thing is not to stop questioning. Curiosity has its own reason for existing. One cannot help but be in awe when he contemplates the mysteries of eternity, of life, of the marvelous structure of reality. It is enough if one tries merely to comprehend a little of this mystery every day. Never lose a holy curiosity.



May 30th, 2009 at 01:05
[...] and Ajaxian. However, I wanted a way to classify this functionality using prototype. I found this post by eternal, which is close to what I wanted but still not quite. So I took it and modified it the [...]