Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent JavaScript from locking up browser on big loop

I have a loop which needs to be run 200 million times in a browser. It's a simulator which several people need to use regularly. It takes about 15 minutes to run, but during this time, the browsers will frequently pop up a warning with "this script is taking too long" etc., and it completely hangs Firefox during the function. This also means the page does not update my status indicator (which is just a number).

I have googled "javascript yield" and read the first 4 pages of hits. Some discuss a new "yield" keyword, but there is only one description and example, which I find incomprehensible, e.g. "The function containing the yield keyword is a generator. When you call it, it's formal parameters are bound to actual arguments, but it's body isn't actually evaluated". Does yield yield to the UI?

One of the few solutions I did find is this old post which uses the deprecated callee argument and a timer to call itself: http://www.julienlecomte.net/blog/2007/10/28/

However, the above example doesn't contain any loop variables or state, and when I add these it falls apart, and my net result is always zero.

It also doesn't do chunking, but I have found some other examples which chunk using "index % 100 == 0" on every iteration. However, this seems to be a slow way of doing it. E.g. this:

How to stop intense Javascript loop from freezing the browser

But it doesn't have any way to update progress, and doesn't yield to the UI (so still hangs the browser). Here is a test version which hangs the browser during execution:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script>
var spins = 1000000
var chunkSize = 1000;
var chunk;
function Stats() {this.a=0};
var stats = new Stats();               
var big;

var index = 0;

var process = function() {
  for (; index < spins; index++) {
    stats.a++;
    big = (big/3.6)+ big * 1.3 * big / 2.1;
    console.write(big);
    // Perform xml processing
    if (index + 1 < spins && index % 100 == 0) {
        document.getElementById("result").innerHTML = stats.a;
        setTimeout(process, 5);
    }
  }
  document.getElementById("result").innerHTML = stats.a;
};


</script>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>
</head>

<body  onload="process()">
<div id=result>result goes here.</div>
</body>
</html>

and here is another attempt which the stats.a is always zero (So I presume there is some scoping issue):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script>
var spins = 1000000
var chunkSize = 1000;
var chunk;
function Stats() {this.a=0};
var stats = new Stats();               

function doIt() {
    function spin() {
       for (spinIx=0; (spinIx<chunkSize) && (spinIx+chunk < spins); spinIx++) {
           stats.a++;
       }
    }        

    for (chunk =0; chunk < spins; chunk+=chunkSize){
        setTimeout(spin, 5);
            document.getElementById("result").innerHTML = stats.a;
        }
      document.getElementById("result").innerHTML = stats.a;
}

</script>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>
</head>

<body  onload="doIt()">
<div id=result>result goes here.</div>
</body>
</html>

I've spent 48 hours trying to get this working - either I am very dumb or this is very hard. Any ideas?

Several people have suggested web workers. I tried several days to get his working, but I could not find a similar example which passes a number etc. The code below was my last attempt to get it working, but the result is always 0 when it should be 100000. I.e. it fails in the same way that my second example above fails.

spinMaster.html:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<script>
if(typeof(Worker)==="undefined") {
  document.write("<h1>sorry, your browser doesnt support web workers, please use firefox, opera, chorme or safari</h1>");
  } 
var worker =new Worker("spinWorker.js");

worker.postMessage({times:1000000});

worker.onmessage=function(event){
document.getElementById("result").innerHTML=event.data;
}; 
</script>

<div id="result">result goes here</div>
</body>
</html>

spinWorker.js

function State() {
    this.a=0;
}

var state = new State();

self.addEventListener('message', spin, false);

function spin(e) {
    var times, i;

    times = e.data.times;
//times = 1000000; // this doesnt work either.

    for(i;i<times;i++) {
        state.a++;
    }

    self.postMessage(state.a);
}

resultant output: 0

like image 302
John Little Avatar asked Dec 28 '12 02:12

John Little


2 Answers

Web workers sound like the better solution.

I wrote this up quickly so i dunno if itll work. Performance will be very bad...

Edit: Changed to same format as poster. Tested

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script>
    var spins = 1000000
    var chunkSize = 1000;
    var chunk;
    function Stats() {this.a=0};
    var stats = new Stats();        
    var big = 0.0;

    var index = 0;

    function workLoop() {

        index += 1;

        stats.a++;
        big = (big/3.6)+ big * 1.3 * big / 2.1;
        console.log(big);
        // Perform xml processing
        document.getElementById('result').innerHTML = stats.a;
        if (index < spins) {
            setTimeout(workLoop, 5); 
        }   

    }   



</script>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>
</head>

<body onload="workLoop()">
<div id="result">result goes here.</div>
</body>
</html>
like image 89
rissicay Avatar answered Oct 03 '22 14:10

rissicay


since JS is single threaded usually, I don't think there is any way around it. If you are supporting only newer browsers however, you might want to look into web workers, which can spawn a new thread to do work: http://developer.mozilla.org/en-US/docs/DOM/Using_web_workers

the only downside I can think of is that I've read it is hard to debug Web Workers because dev tools (Chrome Dev tools unless running dev channel, firebug), doesn't support profiling for it.

here's a nice tutorial to get you started: http://net.tutsplus.com/tutorials/javascript-ajax/getting-started-with-web-workers/

like image 36
kennypu Avatar answered Oct 03 '22 16:10

kennypu