使用setTimeout了解异步

时间:2016-03-07 01:03:49

标签: javascript

我有一个UI,我需要动画才能顺利运行。每隔一段时间,我就需要进行一次半大数据计算,使动画跳过,直到计算完成为止。

我试图通过使用setTimeout进行数据计算 async 来解决这个问题。像setTimeout(calcData(), 0);

这样的东西

整个代码就像这样(简化):

while (animating) {
    performAnimation();
    if (needCalc) {
       setTimeout(calcData(), 0);
    }
}

但我仍然在动画中跳过。当我不需要进行任何数据计算时,它运行顺畅。我怎样才能有效地做到这一点?谢谢!

3 个答案:

答案 0 :(得分:1)

首先,让我们来谈谈代码中发生了什么:

while (animating) {
    performAnimation();
    if (needCalc) {
       // it should be setTimeout(calcData, 0);
       setTimeout(calcData(), 0);
    }
}

在行setTimeout(calcData(), 0);中,您实际上并不推迟调用calcData函数,而是调用它,因为您在函数名后使用()运算符。

其次,让我们想一想,当您真正推迟在上面的代码中调用calcData时会发生什么:通常JavaScript在一个线程中运行,所以,如果您有类似的代码这样:

setTimeout(doSomething, 0);
while (true) {};
永远不会调用

doSomething,因为javascript的解释器永远执行while循环,并且它没有“空闲时间”来执行其他事情(甚至是UI)。 setTimeout - 只是说当解释器空闲时可以安排doSomething的执行,是时候执行这个功能了。

因此,当浏览器执行javascript函数时,所有其他东西都会冻结。

<强>解决方案

  1. 如果您需要处理大数据,可能最好在后端进行计算,然后将结果发送到前端。

  2. 通常,当您需要进行一些计算并渲染结果时,最好使用requestAnimationFrame而非while循环。浏览器将尽快执行requestAnimationFrame中传递的函数,同时也为浏览器提供处理其他事件的时间。您可以使用requestAnimationFrame game查看平滑重绘(分步教程here)。

  3. 如果你真的想在前端部分处理大量数据,并且想要使ui工作顺利,你可以尝试使用WebWorkers。 WebWorkers看起来像JavaScript中的线程,您需要通过将消息从一个传递到另一个并返回来在主UI“线程”和WebWorker之间进行通信,并且WebWorker上的计算不会影响UI线程。

答案 1 :(得分:1)

您正在看到跳过,因为一次只能运行一个javascript线程。当异步完成某些操作时,javascript引擎会将其放入队列以便稍后运行,然后找到要执行的其他内容。当队列中的某些东西需要完成时,引擎会将其拉出并执行它,阻止所有其他操作直到它完成。然后引擎将其他东西从队列中拉出来执行。

因此,如果您希望允许渲染顺利运行,则必须将计算分解为多个异步调用,从而允许引擎在计算之间安排渲染操作。如果你只是迭代一个数组,这很容易实现,所以你可以做类似的事情:

var now=Date.now;
if(window.performance&&performance.now){//use performace.now if we can
    now=performance.now;
}

function calculate(){
    var batchSize=10;//If you have a exceptionally long operation you may want to make this lower.
    var i=0;
    var next=function(){
        var start=now();
        while(now()-start<14){//14ms / frame
            var end=Math.min(i+batchSize,data.length);
            for(;i<end;i++){//do batches to reduce time overhead
                do_calc(data[i]);
            }
        }
        if(i<data.length) setTimeout(next,1)//defer to next tick
    };
    next();
}
calculate();


function render(){
    do_render_stuff();
    if(animating) {
        requestAnimationFrame(render);//use requestAnimationFrame rather then setTimeout for rendering
    }
}
render();

更好的是,如果可以,您应该使用在不同线程中工作的WebWorkers,与主js引擎完全分离。但是,如果您需要在WebWorker中执行某些操作(例如操作DOM树),则会遇到这种情况。

答案 2 :(得分:0)

大多数情况下,您的问题可归结为您对setTimeout()的错误使用

 setTimeout(calcData(), 0);

setTimeout的第一个参数是对要调用的函数的引用。在你的代码中,你没有引用calcData函数,你正在调用它,因为你在函数名之后包含了()。

其次,你为延迟设置0的事实并不意味着你在函数运行之前会有0秒的延迟。 JavaScript在单线程上下文中运行。 setTimeout函数放在一个队列中,并在JavaScript引擎可用时执行,但不会超过最短10ms或您指定的数量(以较小者为准)。

实际上,你的行应该是:

   setTimeout(calcData(),10);