Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using setTimeout to improve responsiveness

When looking to improve a page's performance, one technique I haven't heard mentioned before is using setTimeout to prevent javascript from holding up the rendering of a page.

For example, imagine we have a particularly time-consuming piece of jQuery inline with the html:

$('input').click(function () {
    // Do stuff
});

If this code is inline, we are holding up the perceived completion of the page while the piece of jquery is busy attaching a click handler to every input on the page.

Would it be wise to spawn a new thread instead:

setTimeout(function() {
    $('input').click(function () {
        // Do stuff
    })
}, 100);

The only downside I can see is that there is now a greater chance the user clicks on an element before the click handler is attached. However, this risk may be acceptable and we have a degree of this risk anyway, even without setTimeout.

Am I right, or am I wrong?

like image 328
cbp Avatar asked Feb 25 '11 05:02

cbp


2 Answers

The actual technique is to use setTimeout with a time of 0.

This works because JavaScript is single-threaded. A timeout doesn't cause the browser to spawn another thread, nor does it guarantee that the code will execute in the specified time. However, the code will be executed when both:

  1. The specified time has elapsed.
  2. Execution control is handed back to the browser.

Therefore calling setTimeout with a time of 0 can be considered as temporarily yielding to the browser.

This means if you have long running code, you can simulate multi-threading by regularly yielding with a setTimeout. Your code may look something like this:

var batches = [...]; // Some array
var currentBatch = 0;

// Start long-running code, whenever browser is ready
setTimeout(doBatch, 0);

function doBatch() {
    if (currentBatch < batches.length) {
        // Do stuff with batches[currentBatch]

        currentBatch++;
        setTimeout(doBatch, 0);
    }
}

Note: While it's useful to know this technique in some scenarios, I highly doubt you will need it in the situation you describe (assigning event handlers on DOM ready). If performance is indeed an issue, I would suggest looking into ways of improving the real performance by tweaking the selector.

For example if you only have one form on the page which contains <input>s, then give the <form> an ID, and use $('#someId input').

like image 153
David Tang Avatar answered Nov 13 '22 14:11

David Tang


setTimeout() can be used to improve the "perceived" load time -- but not the way you've shown it. Using setTimeout() does not cause your code to run in a separate thread. Instead setTimeout() simply yields the thread back to the browser for (approximately) the specified amount of time. When it's time for your function to run, the browser will yield the thread back to the javascript engine. In javascript there is never more than one thread (unless you're using something like "Web Workers").

So, if you want to use setTimeout() to improve performance during a computation-intensive task, you must break that task into smaller chunks, and execute them in-order, chaining them together using setTimeout(). Something like this works well:

function runTasks( tasks, idx ) {
   idx = idx || 0;
   tasks[idx++]();
   if( idx < tasks.length ) {
      setTimeout( function(){ runTasks(tasks, idx); },1);
   }
}
runTasks([
   function() {
      /* do first part */
   },
   function() {
      /* do next part */
   },
   function() {
      /* do final part */
   }
]);

Note:

  1. The functions are executed in order. There can be as many as you need.
  2. When the first function returns, the next one is called via setTimeout().
  3. The timeout value I've used is 1. This is sufficient to cause a yield, and the browser will take the thread if it needs it, or allow the next task to proceed if there's time. You can experiment with other values if you feel the need, but usually 1 is what you want for these purposes.
like image 24
Lee Avatar answered Nov 13 '22 14:11

Lee