1

I have some Javascript code that is very process intensive and it is triggering the “unresponsive script” warning. Each of the coded steps have to occur in the order in which they are coded. I think I have located the offending function but I don’t understand how to make it work without triggering the warning.

I found an indication of something that might help here (setTimeout) Javascript: Unresponsive script error but this was really vague so I kept looking. Here is a much better example but I cant see a way to implement this in my code.. How can I give control back (briefly) to the browser during intensive JavaScript processing? The original article http://www.julienlecomte.net/blog/2007/10/28/ is by all accounts genius but I cant seem to figure it for implementation here.

Here is the code I believe is causing the fit.

// -----------------------------------
//  Output stats for all nations
//  by continent in a colon delimited
//  list further delimited by a ~
//  ( ContinentName:NationPower:NationHTML )
//  expects the output from the 
//  continent_stats function
// -----------------------------------

function Nations(ContinentData) {

    document.write("<tr><th>Nation Stats</th></tr><tr>"); // setup progress bar
    var NationString = ""; // init the string
    var Carray = ContinentData.split("*"); //continents
    for (cit = 0; cit < Carray.length; cit++) { // go through the continents
        var Cstat = Carray[cit].split(":"); // make an array of the individual continent stats
        var NumberOfNations = Cstat[4]; // nation #
        var ContinentName1 = Cstat[0]; // Continent Name
        document.write("<td class='ProgressBarText'>" + ContinentName1 + "</td><td class='ProgressBars'>"); // Format loader screen text
        for (nnum = 0; nnum < NumberOfNations; nnum++) { // go through the number of nations on the continent
            var nat1 = BuildCulture(); // build the nation
            var Natname= "Nation"+padLeft(nnum,2,"0"); // name the nation
            NationString = NationString + ContinentName1 + ":" + Natname + ":" + nat1 + "~"; // build the string
            document.write("█"); // add to progress bar
        }
        document.write("</td><td>"+NumberOfNations+ " Nations</td></tr>"); // progress bar complete
    }
    document.write("</table>"); // end the loader screen table
    // send the nation string back
    return NationString;
}

So you can see that it cycles through continents and creates nations for each continent. The BuildCulture() function is the culprit. By itself it works just fine but string 8 or 9 together over the course of around 4 continents and the warning goes off.

I have tried using

setTimeout( function() { BuildCulture(); }, 1000);

all over the place, in the main code section, in the BuildCulture() function beginning and end, in the Nations(ContinentData) function in and out of the loops. It never works.

I am clearly looping too much but I need every loop. Will SetTimeout help me at all or am I chasing the wrong statement?

If SetTimeout is my target for a solution, how can I implement it in this code?

Thanks much.

P.S. I am only aiming for this to work in Firefox so compatibility with IE core browsers is not necessary.

Community
  • 1
  • 1
  • Yes it is possible (and not particularly difficult) to rewrite a loop using `setTimeout()`, though it becomes more of a hassle if you want to do it for a nested loop. Three questions: (1) The `Nations()` function returns a string, but how is that return value used? (2) How does `BuildCulture()` work? (Does it use global variables, or otherwise know which loop iteration it's in?) (3) Does that progress bar stuff actually work for you? Because I wouldn't expect the browser to actually update the display until the JS is finished, though you could do a progress bar if using `setTimeout()`. – nnnnnn Jul 03 '12 at 05:58
  • P.S. The reason I asked those questions is that the easiest way to rewrite that function to avoid the "unresponsive script" would be to change the _outer_ loop to use `setTimeout()`, but I wouldn't want to start without knowing what happens to the return value, and how `BuildCulture()` works. Does that "unresponsive" message occur if you only do one continent (i.e., only one iteration of outer loop)? – nnnnnn Jul 03 '12 at 06:05

2 Answers2

0

First, browser is single thread application. All events, timers and interaction is done linear, step by step. So you can not spawn new thread or do parallel calculations.

In that sense, setTimeout could hypothetically help if you could cut function Nations and call it in setTimeout feeding it previous result. But that's not optimal ;)


Second, Do NOT touch the DOM!!!
JS might be fast, but touching the DOM (getElements, document.write, innerHTML...) is SLOW!

In your case rewrite function Nations(ContinentData) to output one string that you will innerHTML to some dom element.
Point is that all calculation and preparation will be done in (relatively) fast JS engine and the result will be applied or innerHTML-ed to very slow DOM.

Or you could use createElem and addChild using DOM objects. Doesn't matter. Both ways have advantages and disadvantages but both do the same thing.


Third: try with web workers. They are doing good job of simulating 'threads':
http://ejohn.org/blog/web-workers/
http://www.sitepoint.com/javascript-threading-html5-web-workers/
http://www.w3schools.com/html5/html5_webworkers.asp

CoR
  • 3,826
  • 5
  • 35
  • 42
0

Okay, since I couldn't run this anywhere, there may be a few errors - but this is the pattern you want to go for IF you NEED to use timeouts.

However, given the structure & style of your code, I get the impression that you may be going about this the wrong way.

For instance, where is your data coming from? Why is your structured data represented with strings? Are you doing all of this computation on the client-side? Is it a game? What is buildCulture() doing that's taking so long? In anycase, you should perhaps look into JSON, and asynchronous loading (aka AJAX).

function Nation(continentData, renderNationComplete) {
  // begin our table
  var str = '<table>',
      continents = continentData.split('*');

  // use the `+=` operator to add string together
  str += '<tr><th>Nation Stats</th></tr><tr>';

  // This will act like a `for` loop - 
  // the difference is that we can control the iterations
  var continentsCount = 0;
  function renderContinents(){
    if(continentsCount < continents.length) {
      var stats = continent[continentsCount].split(':'),
          nNations = stats[4],
          cName = stats[0];

      str += '<td class="ProgressBarText">' + cName + '</td><td class="ProgressBars">';

      var nationCount = 0;
      function renderNation(){
        if(nationCount < nNations) {
          var culture = BuildCulture(),
              nName = "Nation" + padLeft(nationCount, 2, "0");

          str += cName + ':' + nName + ':' + culture + '~';
          str += '█'; // not precisely sure what this is for.

          nationCount++;
          // renderContinents won't proceed till we're done with all the 
          // renderNations for this particular continent.
          setTimeout(function(){ renderNation(); }, 1000);
        } else {
          // close up the rows
          str += '</td><td>' + nNations + ' Nations</td></tr>';
          nationCount++;
          // this timeout may not be necessary, you'll have to tweak the value.
          setTimeout(function(){ renderContinents(); }, 1000);
        }
      } renderNation();
    } else {
      str += '</table>';

      // everything is done, send it back.
      // note: this function does not return anything,
      // instead, it keeps on running asynchronously till
      // it's done, at which point it call the callback
      // that you pass in when it is instantiated.
      renderNationComplete(str);
    }
  } renderContinents();
}

// here we call the Nation function, and pass in another function
// that we want to be called when everything is done.
// in this case that function returns a string of html, that we can
// then add to the DOM.
Nation(data, function(html){
  var el = document.getElementById('tableArea');
  el.innerHtml(html);
});
Jon Jaques
  • 4,262
  • 2
  • 23
  • 25
  • Having not looked at the JulienLecomte site before I wrote this, I'm glad my implementation is pretty close to his :) Note: he's using arguments.callee, but that is deprecated (notice the post is from 2007) - however the pattern itself is solid. – Jon Jaques Jul 03 '12 at 06:07