2016-08-15 60 views
3

有谁知道什么可以导致传递到setTimeout(func, 0)被调用的函数显着延迟?从setTimeout(func, 0)执行时间和func实际调用时间之间,我看到延迟超过1.2秒。什么可以导致从setTimeout回调延迟(回调,0)

我只看到这种行为与Chrome v52(v51没有它)。所以,这可能是一个Chrome的问题,但在我报告它是一个错误之前,我想调查它。

我不幸没有一个可以重新使用它的小案例。它发生在AngularJS应用程序中,以响应滚动事件。我们通过鼠标滚轮来处理滚动事件触发器,并且作为其中的一部分,请调用setTimeout(func, 0)来完成一些后续工作。

使用Chrome devtools,我可以看到在这个1.2秒的差距期间只有很少的JavaScript运行。我没有阻止线程。事实上,我使用console.profile和console.profileEnd来分析特定的1.2秒图像。它显示它在99%的时间里处于闲置状态。

看devtools中的时间线,我在那个时间段内看到的唯一一件事就是处理“Mouse Wheel”事件。他们中的很多人(差距的前1秒每5毫秒一次)。这本身有点奇怪,因为在那段时间鼠标滚轮没有滚动。我做了一个Mac Magic Mouse的虚拟滚轮的轻拂,我们撞到了可滚动区域的顶部。事件发生后,我们不断收到鼠标滚轮事件。

有没有人有任何想法?可以很快执行一小段鼠标滚轮事件来延迟回调的调用吗?有什么方法可以调查为什么超时回调在其调度时间后1.2秒被调用?

编辑:添加了描绘setTimeout显示回调的时间线图像。

Timeline of slow setTimeout

+1

你肯定需要一个可复制的例子来报告它的错误,所以请不要犹豫,发布它。 – estus

+0

'setTimeout(func,0)'将函数放在队列中,并调度它以调用下一个事件循环或下一个“记号”,换句话说。由于浏览器仅为UI使用单个线程,因此可能会造成一些问题。鼠标滚轮事件可能是相关的,但我不认为有一个明确的方式来说明没有检查他们在做什么和为什么。 – vlaz

+0

我添加了一个显示时间线的图像。控制台正在记录何时调用'setTimeout'方法以及何时执行回调。在这个例子中,它需要超过1.4秒。摘要图显示我们大多数时间都处于闲置状态。它真的可能是有人在窃取线程吗? – Steven

回答

-1

这里发布到包括图像

的setTimeout(FUNC,0),而您的代码运行的保证,在0秒,如果有已经在任务列队事件队列(最有可能通过滚动触发),那么他们将不得不完成被放入执行堆栈并运行。

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

event loop and task queue

如果您需要立即执行这一形象,你需要确保在你的滚动页面时任务队列中添加任何情况下,这意味着没有事件监听器如.on('scroll', func)或在注册func之前执行setTimeout .on('scroll')

+0

我可以非常明白,它不能保证在0秒运行,但我希望它会比1.2秒后运行得更快。 我已将图像添加到显示事件发生时间表的原始帖子。我添加了控制台输出来标记时间线,以及执行setTimeout调用的时间以及何时获得回调。在这个例子中,它花费了1.4秒。 摘要图还显示我们在等待回调的大部分时间都处于闲置状态。那么,真的可能是另一个阻碍我们的事件吗? – Steven

+0

我不知道为什么我放弃了投票,但你问“能够很快执行的一小段鼠标滚轮事件会延迟回调的调用吗?”答案很可能是肯定的,这取决于onscroll上回调的内容是什么 – David

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事件将尽快结束。

+0

*“......一个[滚动事件]每间隔5秒大约差距的前1秒......”*:即200个滚动事件在1秒内。*“在此期间鼠标滚轮不滚动”:这只能意味着JavaScript无法跟上事件队列,无论开发工具如何。 – trincot

+0

这也被称为“debouncing”函数调用。另见https://davidwalsh.name/javascript-debounce-function –