Sunday, February 19, 2012

Javascript - How to keep it responsive?

One remark before you start reading the described problem and solution do work very similar in Flash as shown in a rather complex scenario here.

In case you do not want to read the background story click: shut up and show me the solution.

In my last project I was assigned to create a dashboard for presenting the key financial numbers of our company. In order to present these numbers not only on desktop but also on mobile devices - every developer knows what is going to come - mainly the iPad the application needed to be done in html for compatibility reasons.

Since the Internet Explorer (7 and above) is one of the official supported browsers in our company I was facing a particular problem. Whenever I opened a page with several dashboards and tables it was just running fine. When I navigated to a page showing around 15 dashboards the rendering of the dashboards took like 3 to 5 seconds. Since not all the versions of the Internet Explorer do support the Canvas element (which is desperately needed here) i had to use a javascript library which emulates the Canvas element in IE 7 and 8. This is a very nice library and worth a try http://excanvas.sourceforge.net/ Nevertheless it is either the javascript engine performance or the Canvas emulation or a combination of these two things. The result stands it takes 3 to 5 seconds, by this amount of loading time people tend to say "It's broken". In all the other browsers it worked significantly faster and even in IE with most of the pages just some not.


So let's get to the real problem, first thought is use the easy approach to let the people know something is going on so put a loading indicator there. Hmm.. nice thing, but the way javascript works this does not cut it. The problem here is as long as something is running in javascript the webpage is not refreshing. So no matter how many loading indicators you will put none of them is going to move. As long as you are just showing a text message or a static picture it is fine but if you want to show something animated like a animated gif it is not.


Everybody used to desktop application development as I am, e.g. with .NET, JAVA or Objective-C knows the concept of multi-threading which is the usual way out of the problem: "My UI is not responsive". Javascript is a in Browser language and for the sake of simplicity it is running in a single thread - the UI thread. That means everything that is going to happen is executed there and cannot be scheduled to run in parallel or in the background. Normally that is fine as there are some components in the browser working like this, e.g. data loading from the server therefore called AJAX (first a for asynchronies - meaning it is running in the background). But what can you do if you do have a longer running javascript code and you do want the user to see something moving in the UI.


Here comes the solution the setTimeout function. This function allows you to delay the execution of code by a certain amount of time. Since the function is not executed in the javascript environment but in the browser environment the browser considers the javascript execution done at least for a short timeframe and therefore uses the opportunity to update the UI. After the delay elapsed you can continue to process your longer running operation. Having said that you can already imagine what the trouble with this approach is. You do need to split your operations into shorter running operations and store the computed information so that you can continue on the next part. In case you are using jquery you can nicely structure the control flow. The last thing you need to define is an end condition so that it does turn in an endless loop. In the following I created a little example which is just counting to a certain number. Without any timeout you would count like this.

<html>
<head>
<script type="text/javascript">
  function count() { for(var i = 0; i < 2000000000; i++) {}
  alert("counting done"); }
</script>

</head>
<body>
<input type="button" value="Button" onClick="count();"> </body>
</html>

The execution freezes the website until it is done, depending on the number it might even freeze so long that the browser reports to you and stops the script from further executing. When restructuring the code to the following this problem will be solved.

<html>
<head>

<script type="text/javascript">
  var number = 0;
  var interval = 10000000;

  function count(target) {
    var nextTarget = number + interval;
    // end of counting is reached
    if (target < nextTarget) {
      nextTarget = target;
    }
    for(; number < nextTarget; number++) {}
    // stop the recursion
    if (nextTarget >= target) {
      alert("counting done");
    }
    else {
      setTimeout(function() { count(target); }, 5);
    }
  }
</script>

</head>
<body>
<input type="button" value="Button" onClick="count(2000000000);">
</body>
</html>


Ok I did not say that this comes for free :) the code now is a little more difficult and you have to consider what the user might do while the operation is ongoing. I especially see a problem when the user is leaving the page or triggers some sort of other operation it would be beneficial to cancel the current operation to not lead the browser into long running unnecessary operations. This can be achieved by additional variables to just stop the processing in case something happens it just requires extra work and attention from the developer to do so.

No comments: