How to deplay running a function in javascript
Recently when I am trying to use resemble.js to compare images a problem rasies: the computation is CPU intensive and slows the whole page a lot. While researching the solution I came across the concept of Web Workers API.
Web Workers provide a simple means for web content to run scripts in background threads. The worker thread can perform tasks without interfering with the user interface. In addition, they can perform I/O using XMLHttpRequest (although the responseXML and channel attributes are always null). Once created, a worker can send messages to the JavaScript code that created it by posting messages to an event handler specified by that code (and vice versa.) This article provides a detailed introduction to using web workers.
I tried to take advantage of this feature immediately since it already exists in most modern web browsers. However, soon I found the limits of Web Workers:
When web workers run in the background, they do not have direct access to the DOM but communicate with the document by message passing. This allows for multi-threaded execution of JavaScript programs.
Unfortunately, resemble.js relies heavily on the HTML5 canvas to get pixels from a image src, and this is one of the most time-consuming part in the process. After I refractored resemble.js and moved as many computation as possible to worker thread, the speed was boosted by 100% but problem remained - page still freezed.
Finally I realized I should sacrifice performance to make the page still usable while resemble.js is computing - either in main thread or worker thread. So I decided to use setTimeout and wrote a small class to help me:
/**
* Queue a list of functions and run them in order. Can be used to run javascript without blocking
* ui.
*/
(function ($) {
$.fqueue = {
_timer: null,
_queue: [],
_runnext: function () {
var next = $.fqueue._queue.shift();
if (next) {
next.call();
} else {
clearTimeout($.fqueue._timer);
$.fqueue._timer = null;
}
},
_delayrun: function () {
$.fqueue._timer = setTimeout(function () {
$.fqueue._runnext();
$.fqueue._delayrun();
}, 500);
},
runall: function () {
if ($.fqueue._timer != null)
return; // already started
$.fqueue._delayrun();
},
queue: function (fc) {
if (fc) {
$.fqueue._queue.push(fc);
}
}
}
})(jQuery);
Basically I for each image I use $.fqueue.queue
to queue the computation and call $.fqueue.runall
at the end. Since name $.queue
is alreay taken I choose $.fqueue
instead.