什么可能导致setTimeout回调延迟(回调,0)

时间:2016-08-15 18:27:03

标签: javascript angularjs google-chrome

有没有人知道什么会导致调用传递给setTimeout(func, 0)的函数出现重大延迟?我发现执行setTimeout(func, 0)和实际调用func之间的延迟超过1.2秒。

我只看到Chrome v52的这种行为(v51没有它)。因此,它可能是Chrome问题,但在我将其报告为错误之前,我想对其进行调查。

我很遗憾没有一个重复它的小案例。它发生在AngularJS应用程序中以响应滚动事件。我们通过鼠标滚轮处理滚动事件触发器,并作为其中一部分,调用setTimeout(func, 0)进行一些后续工作。

使用Chrome devtools,我可以看到在这1.2秒间隙内运行的javascript非常少。我没有阻止线程。实际上,我使用console.profile和console.profileEnd来分析特定的1.2秒grap。它显示它在99%的时间内处于空闲状态。

看看devtools中的时间线,我唯一能看到在那个时间段内开火的是对鼠标轮的一些处理"事件。它们中有很多(差距的前1秒左右每5毫秒一个)。这本身有点奇怪,因为鼠标滚轮在此期间没有滚动。我只需轻弹一下Mac Magic Mouse鼠标的虚拟滚轮,就可以进入可滚动区域的顶部。在我们进入顶部之后的事件,我们继续获得鼠标滚轮事件。

有没有人有任何想法?可以快速执行的一小段鼠标滚轮事件会延迟回调的调用吗?有没有办法调查在调度时间后1.2秒调用超时回调的原因?

修改:添加了时间轴图片,描述了setTimeout的show callback。

Timeline of slow setTimeout

2 个答案:

答案 0 :(得分:1)

setTimeout(..., 0)将事件放入事件队列中。但它没有优先于已经放入队列的其他事件。当然,当你处理滚动事件时,你会遇到许多滚动事件被放入队列的情况,而滚动事件处理程序代码仍在运行,处理前一个滚动事件。

因此,事实上,在处理滚动事件时,您很容易陷入延迟。当您的调用堆栈为空时,即当前正在运行的事件处理程序完成时,将从事件队列中获取下一个事件。即使某个队列中存在与超时相关的事件,也不会获得更高的优先级:所有事件都必须等待轮流。

例如,如果队列中已有10个滚动事件,则它们都必须被处理,当鼠标仍在滚动时,问题只会变得更糟,因为它可以添加更多队列,以及代码可以处理它们的更高速率。

解决此问题的一种方法是使您的滚动事件处理程序尽可能轻,并以异步方式执行重要操作。如果处理程序检测到在之前的运行中某些事情已经安排以这种方式运行,它可以简单地取消该计划任务,并将其替换为新版本。

示例代码:

var task = -1;
window.addEventListener('scroll', function(e) {
    clearTimeout(task);
    task = setTimeout(function () {
        // all the (heavy) processing related to scrolling comes here.
    }, 0);
};

不要将此setTimeout与问题中的setTimeout混淆。但请注意,当您启动另一个setTimeout时,它将按顺序处理。上面的代码只是将实际处理移动到队列的末尾。但是如果有更多滚动事件,他们将从队列中删除该超时事件,并在队列的 end 中添加一个新事件。

这将使您实际上对滚动处理程序代码的重要部分执行的次数减少,这可能会对某些动画的流动性产生一些负面影响。但是它会阻止落后,作为奖励,你的其他X = 1 : 50; Y = [-1,1]; Y = Y(randi([1,2], 1, 50)); % create random dataset stem (X, Y, ... % "..." allows you to continue below 'linestyle', '--', ... % dotted lines 'linewidth', 3, ... % width of '2' 'color', 'k', ... % black color lines 'markeredgecolor', 'r', ... % red outline for markers 'markerfacecolor', 'green', ... % green 'filling' for markers 'markersize',15); % bigger markers axis([0,50,-1.5,1.5]); % adjust axis limits 事件将会更快结束。

答案 1 :(得分:-1)

在此处发布以包含图片

setTimeout(func,0)不保证你的代码会在0秒内运行,如果在任务队列中已经排列了事件(很可能是通过滚动触发)那么它们必须完成放置在执行堆栈中运行。

https://www.youtube.com/watch?v=8aGhZQkoFbQ

查看此图片

event loop and task queue

如果需要立即执行,则需要确保在滚动页面时没有在任务队列中添加任何事件,这意味着没有像.on('scroll', func)之类的eventListeners,或者在注册func之前执行setTimeout .on('scroll')