How to defer or postpone execution of some Javascript code

— Just a small tip i’d like to share with you —

The issue :

Since Javascript is single threaded, during the execution of a piece of code :
• Changes to the DOM won’t show.
• Event handlers won’t trigger (mouse, keyboard, network, …).
• Drawings made on the canvas won’t show.
This can be annoying for a long computation, for which you’ll most probably want to see the ongoing progress, and also want the user to be able to cancel the operation.
You might also want to postpone / defer the execution of some code after every operation ended, if – for instance – you are sorting all your sorted list after every insertions/deletion operations occurred. No need to create a new (and most likely complex) control path, you just have to say ‘do this after you finished the rest’.

The common – and wrong -solution : setTimout.

A common way to solve this issue is to split your computation into smaller batches, and use a setTimeout(resumeCompute, 0) when one batch is done, to trigger the next execution while allowing the Browser to ‘breathe’, that is update the DOM and listen to events.

While seducing enough (and working), reading the specifications of setTimeout reminds us that we have no guaranty on when using a Zero delay setTimeout will trigger at once. Latest (late 2015) browsers will actually trigger the call 5 milliseconds later  (So yes 0 == 5, another javascript oddity 🙂 ), and depending on your software constraints, it might be a BIG issue (video games have a 16.6ms time frame, 5 ms is just too much).

The real solution : postMessage.

postMessage is meant to allow communication in between frames of the same web page. It is handled through the common event system, and thus a call to postMessage will act as triggering an event that will pile on the event queue, that, later, when the javascript engine finished its work, will be dispatched on any listener.
So you probably got the trick already : if we send a message to ourselves, we have a way to postpone / delay / defer the execution of whatever code after current call stack returned !!!

The code is very simple for a single task that you’d like to segment into chunks :

window.addEventListener('postMessage', resumeTheExecution);

Used with

postMessage('','*');

But you might want some flexibility, so, by using the mighty new Map object, we can build a postpone function that will act even more simply as a 0 setTimeout call.
So the use will be like :

postpone(someCallback);

The code for postpone :

var postpone = (function () {
     var fnMap = new Map(), idMap = new Map(), fnId = 0;
     var msg = { fnId: 0 };
     function _postpone(fn) {
          if (!fnMap[fn]) {
                            fnId++;
                            fnMap[fn] = fnId;
                            idMap[fnId] = fn;
                          }
           msg.fnId = fnMap[fn];
           postMessage(msg, '*');
     }
 function _postponeListener(e) {
     var fnId = e.data.fnId;
     if (fnId) idMap[fnId]();
 }
 window.addEventListener("message", _postponeListener);
 return _postpone;
}());

Benchmarks

I did a JSFiddle to check the 5 milliseconds issue, and in deed measures shows that a setTimeout doesn’t trigger at once on Firefox and Chrome.
I used the Monte Carlo method to compute PI, and this is quite a long job to get even a 10 digits precision : With postMessage, we can see the progress that gets drawn live on the canvas, and also stop the computation at any time by just pressing a key : way better than to wait in front of a frozen Browser.

http://jsfiddle.net/gamealchemist/L7nhL1hx/

Some Figures :
Time to compute 10.000 batches of 1000 point for the monte carlo method.
//  Display On
Chrome 46  :    postMessage : 22 seconds   setTimeout 76 seconds
Firefox 42   :     postMessage : 17 seconds   setTimeout 65 seconds
// Display Off
Chrome 46  :    postMessage : 1.3 seconds   setTimeout 55 seconds
Firefox 42   :     postMessage : 0.6 seconds   setTimeout 46 seconds

We can clearly see the overhead of setTimeout for each postponed call, which is around 5 ms, that obviously shows even more when processing time is shorter.

That’s quite all about this topic, hope you enjoyed the read, do comment if you want to !

Advertisements
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s