7

我有一个循环需要在浏览器中运行 2 亿次。这是一个需要多人定期使用的模拟器。运行大约需要 15 分钟,但在此期间,浏览器会频繁弹出“此脚本运行时间过长”等警告,并在运行期间完全挂起 Firefox。这也意味着该页面不会更新我的状态指示器(这只是一个数字)。

我搜索了“javascript yield”并阅读了前 4 页的点击量。有些人讨论了一个新的“yield”关键字,但只有一个描述和示例,我觉得难以理解,例如“包含yield关键字的函数是一个生成器。当你调用它时,它的形参绑定到实际参数,但是它的身体实际上并没有被评估”。是否yield屈服于 UI?

我确实找到的少数解决方案之一是这篇旧帖子,它使用已弃用的被调用者参数和一个计时器来调用自己: http ://www.julienlecomte.net/blog/2007/10/28/

但是,上面的示例不包含任何循环变量或状态,当我添加它们时它会崩溃,我的最终结果始终为零。

它也不进行分块,但我发现了一些其他示例,这些示例在每次迭代中都使用“index % 100 == 0”进行分块。但是,这似乎是一种缓慢的方法。例如这个:

如何阻止激烈的 Javascript 循环冻结浏览器

但它没有任何更新进度的方法,也不会屈服于 UI(所以仍然会挂起浏览器)。这是一个在执行期间挂起浏览器的测试版本:

<!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>

这是另一个stats.a总是为零的尝试(所以我认为存在一些范围问题):

<!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>

我花了 48 个小时试图让这个工作——要么我很笨,要么这很难。有任何想法吗?

有几个人建议使用网络工作者。我尝试了几天让他工作,但我找不到一个类似的例子,它传递了一个数字等。下面的代码是我最后一次尝试让它工作,但是当它应该是 100000 时结果总是 0。即它失败了就像我上面的第二个例子失败一样。

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);
}

结果输出:0

4

5 回答 5

4

网络工作者听起来是更好的解决方案。

我写得很快,所以我不知道它是否会起作用。性能会很差...

编辑:更改为与海报相同的格式。经过测试

<!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>
于 2012-12-28T02:42:40.337 回答
1

由于 JS 通常是单线程的,我认为没有办法解决它。但是,如果您只支持较新的浏览器,您可能需要研究 web worker,它可以产生一个新线程来工作:http: //developer.mozilla.org/en-US/docs/DOM/Using_web_workers

我能想到的唯一缺点是我读过很难调试 Web Worker,因为开发工具(Chrome 开发工具,除非运行开发通道,萤火虫)不支持对其进行分析。

这是一个很好的教程,可以帮助您入门: http: //net.tutsplus.com/tutorials/javascript-ajax/getting-started-with-web-workers/

于 2012-12-28T02:40:16.367 回答
1

您在第一次测试中接近了一个工作示例,但我确实看到了一个逻辑错误。在您的 if() 中,您需要从函数返回,否则它将始终运行多个竞争该线程的函数。

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);
        //!!!!!
        return;//without this it'll keep iterating through the for loop without waiting for the next 5ms
        //!!!!!
    }
  }

document.getElementById("result").innerHTML = stats.a;
};

两个样本都不会等待下一个 setTimeout (在您的第二个示例中,您将继续迭代您的 for 循环,直到 for 循环完成,但在每个块大小您为后续迭代设置超时。所有这些都是延迟整个序列持续 5 毫秒,从你的 for 循环开始迭代起,它们仍然会堆积并执行 5 毫秒)

总而言之,您似乎走在了正确的轨道上,两个示例中都只有很小的逻辑错误。

于 2012-12-28T02:57:44.490 回答
0

github 上有一个库可用于这种类型的大循环,无需锁定浏览器/线程,也无需使用 web-workers。

有点实验性,并支持大递归循环,但它可能对你有用。依赖 q.js 来解决 Promise。

https://github.com/sterpe/stackless.js

于 2013-04-06T18:43:54.863 回答
-1

这应该做你想要的:

<!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 stats = {a:0,spins:0};              

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

}

</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>
于 2012-12-28T02:45:46.593 回答