使用 setTimeout 了解异步

Understanding async using setTimeout

本文关键字:异步 了解 setTimeout 使用      更新时间:2023-09-26

我有一个 UI,我需要动画才能流畅运行。每隔一段时间,我需要做一个半大数据计算,使动画跳过,直到这个计算完成。

我试图通过使数据计算与setTimeout异步来解决此问题。类似setTimeout(calcData(), 0);

整个代码是这样的(简化):

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

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

您看到跳过是因为一次只运行一个 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 树,那么您就会陷入困境。

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

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看到流畅的重绘(此处为分步教程)。

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

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

 setTimeout(calcData(), 0);

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

其次,您为延迟输入 0 并不意味着在函数运行之前会有 0 秒的延迟。JavaScript 在单线程上下文中运行。setTimeout 函数被放置在队列中,并在 JavaScript 引擎可用时执行,但不得早于最小 10 毫秒或您指定的量(以较小者为准)。

实际上,您的行应该是:

   setTimeout(calcData(),10);