卡住 setTimeout()s (Chrome)
Stuck setTimeout()s (Chrome)
我遇到这样一种情况,多个 setTimeout()
调用(典型长度:100 毫秒到 1 秒)处于活动状态并且本应关闭,但它们没有触发。 Chrome (Mac) 调试器配置文件在此(可能无限)期间显示“空闲”。配置文件显示没有任何事情发生。没有循环。无代码 运行。没有垃圾收集。没有 activity 任何东西。视口处于活动状态并具有焦点。在我等待(可能是无限时间)之后,当我“做其他事情”时,比如 mouseover
一些不相关的元素——就像 :hover 一样简单——僵局打破了,排队的 setTimeout()
全部火。
当我在这种“冻结”发生后在 setTimeout()
处理程序函数中设置断点时,它们会在僵局中断时按顺序命中,正如您所期望的那样。
不幸的是,复制路径很困难。到目前为止,创建更简单的测试用例只会使复制变得更加困难,或者最终无法复制。
大多数围绕setTimeout()
“问题”的喋喋不休来自不了解jscript等单线程性质的人,因此没有帮助。让我重复一遍:计时器已排队,应该已经触发。正如探查器所证明的那样,浏览器处于空闲状态。计时器最终会触发,但只有在鼠标 activity 发生之后。这种行为对我来说似乎是非常错误的。如果浏览器空闲,并且队列中有事件,它们应该触发。
有人见过这样的行为吗?我是否偶然发现了一种锁定事件调度程序的方法?也许我错过了什么。
更新:无法在 Windows 7.
上复制
更新 2:在 Mac 重新启动 Chrome,无法再复制。因此,可能出现的最坏结果:无法回答为什么会发生、为什么会一直发生、为什么没有可靠地发生、为什么会消失以及为什么不会再发生。
我最近遇到了类似的问题,发现自版本 47 以来,chromium 用户已经决定在他们认为这是 'detrimental to the majority of users' 时不遵守 setTimeout。基本上他们已经弃用了 setTimeout API(像往常一样不问任何人)。
这是 bug 570845 人们发现这件事的地方。关于这个问题还有许多其他错误和讨论线程。
后备方案是使用 requestAnimationFrame 来模拟 setTimeout。
这是一个概念证明:
'use strict'
var PosfScheduler = ( function () {
/*
* Constructor.
* Initiate the collection of timers and start the poller.
*/
function PosfScheduler () {
this.timers = {};
this.counter = 0;
this.poll = function () {
var scheduler = this;
var timers = scheduler.timers;
var now = Date.now();
for ( var timerId in timers ) {
var timer = timers[timerId];
if ( now - timer.submitDate >= timer.delay ) {
if ( timer.permanent === true ) {
timer.submitDate = now;
} else {
delete timers[timer.id];
}
timer.func.apply.bind( timer.func, timer.funcobj, timer.funcargs ).apply();
}
}
requestAnimationFrame( scheduler.poll.bind(scheduler) );
};
this.poll();
};
/*
* Adding a timer.
* A timer can be
* - an interval (arg[0]: true) - a recurrent timeout
* - a simple timeout (arg[0]: false)
*/
PosfScheduler.prototype.addTimer = function () {
var id = this.counter++;
var permanent = arguments[0] ;
var func = arguments[1] ;
var delay = arguments[2] ;
var funcobj = arguments[3] ;
var funcargs = Array.prototype.slice.call(arguments).slice(4);
var submitDate = Date.now() ;
var timer = {
id: id,
permanent: permanent,
func: func,
delay: delay,
funcargs: funcargs,
submitDate: submitDate,
}
this.timers[id] = timer;
return timer;
};
/*
* Replacement for setTimeout
* Similar signature:
* setTimeout ( function, delay [obj,arg1...] )
*/
PosfScheduler.prototype.setTimeout = function () {
var args = Array.prototype.slice.call(arguments);
return this.addTimer.apply.bind( this.addTimer, this, [false].concat(args) ).apply();
};
/*
* Replacement for setInterval - Untested for now.
* Signature:
* setInterval ( function, delay [obj,arg1...] )
*/
PosfScheduler.prototype.setInterval = function () {
var args = Array.prototype.slice.call(arguments);
return this.addTimer.apply.bind( this.addTimer, this, [true].concat(args) ).apply();
};
PosfScheduler.prototype.cancelTimeout = function ( timer ) {
delete this.timers[timer.id];
};
/*
* Don't want to leave all these schedulers hogging the javascript thread.
*/
PosfScheduler.prototype.shutdown = function () {
delete this;
};
return PosfScheduler;
})();
var scheduler = new PosfScheduler();
var initTime = Date.now();
var timer1 = scheduler.setTimeout ( function ( init ) {
console.log ('executing function1 (should appear) after ' + String ( Date.now() - init ) + 'ms!' );
}, 200, null, initTime );
var timer2 = scheduler.setTimeout ( function ( init ) {
console.log ('executing function2 afte: ' + String ( Date.now() - init ) + 'ms!' );
}, 300, null, initTime );
var timer3 = scheduler.setTimeout ( function ( init ) {
console.log ('executing function3 (should not appear) after ' + String ( Date.now() - init ) + 'ms!' );
}, 1000, null, initTime );
var timer4 = scheduler.setTimeout ( function ( init, sched, timer ) {
console.log ('cancelling timer3 after ' + String ( Date.now() - init ) + 'ms!' );
sched.cancelTimeout ( timer3 );
}, 500, null, initTime, scheduler, timer3 );
var timer5 = scheduler.setInterval ( function ( init, sched, timer ) {
console.log ('periodic after ' + String ( Date.now() - init ) + 'ms!' );
}, 400, null, initTime, scheduler, timer3 );
var timer6 = scheduler.setTimeout ( function ( init, sched, timer ) {
console.log ('cancelling periodic after ' + String ( Date.now() - init ) + 'ms!' );
sched.cancelTimeout ( timer5 );
}, 900, null, initTime, scheduler, timer5 );
我遇到这样一种情况,多个 setTimeout()
调用(典型长度:100 毫秒到 1 秒)处于活动状态并且本应关闭,但它们没有触发。 Chrome (Mac) 调试器配置文件在此(可能无限)期间显示“空闲”。配置文件显示没有任何事情发生。没有循环。无代码 运行。没有垃圾收集。没有 activity 任何东西。视口处于活动状态并具有焦点。在我等待(可能是无限时间)之后,当我“做其他事情”时,比如 mouseover
一些不相关的元素——就像 :hover 一样简单——僵局打破了,排队的 setTimeout()
全部火。
当我在这种“冻结”发生后在 setTimeout()
处理程序函数中设置断点时,它们会在僵局中断时按顺序命中,正如您所期望的那样。
不幸的是,复制路径很困难。到目前为止,创建更简单的测试用例只会使复制变得更加困难,或者最终无法复制。
大多数围绕setTimeout()
“问题”的喋喋不休来自不了解jscript等单线程性质的人,因此没有帮助。让我重复一遍:计时器已排队,应该已经触发。正如探查器所证明的那样,浏览器处于空闲状态。计时器最终会触发,但只有在鼠标 activity 发生之后。这种行为对我来说似乎是非常错误的。如果浏览器空闲,并且队列中有事件,它们应该触发。
有人见过这样的行为吗?我是否偶然发现了一种锁定事件调度程序的方法?也许我错过了什么。
更新:无法在 Windows 7.
上复制更新 2:在 Mac 重新启动 Chrome,无法再复制。因此,可能出现的最坏结果:无法回答为什么会发生、为什么会一直发生、为什么没有可靠地发生、为什么会消失以及为什么不会再发生。
我最近遇到了类似的问题,发现自版本 47 以来,chromium 用户已经决定在他们认为这是 'detrimental to the majority of users' 时不遵守 setTimeout。基本上他们已经弃用了 setTimeout API(像往常一样不问任何人)。
这是 bug 570845 人们发现这件事的地方。关于这个问题还有许多其他错误和讨论线程。
后备方案是使用 requestAnimationFrame 来模拟 setTimeout。
这是一个概念证明:
'use strict'
var PosfScheduler = ( function () {
/*
* Constructor.
* Initiate the collection of timers and start the poller.
*/
function PosfScheduler () {
this.timers = {};
this.counter = 0;
this.poll = function () {
var scheduler = this;
var timers = scheduler.timers;
var now = Date.now();
for ( var timerId in timers ) {
var timer = timers[timerId];
if ( now - timer.submitDate >= timer.delay ) {
if ( timer.permanent === true ) {
timer.submitDate = now;
} else {
delete timers[timer.id];
}
timer.func.apply.bind( timer.func, timer.funcobj, timer.funcargs ).apply();
}
}
requestAnimationFrame( scheduler.poll.bind(scheduler) );
};
this.poll();
};
/*
* Adding a timer.
* A timer can be
* - an interval (arg[0]: true) - a recurrent timeout
* - a simple timeout (arg[0]: false)
*/
PosfScheduler.prototype.addTimer = function () {
var id = this.counter++;
var permanent = arguments[0] ;
var func = arguments[1] ;
var delay = arguments[2] ;
var funcobj = arguments[3] ;
var funcargs = Array.prototype.slice.call(arguments).slice(4);
var submitDate = Date.now() ;
var timer = {
id: id,
permanent: permanent,
func: func,
delay: delay,
funcargs: funcargs,
submitDate: submitDate,
}
this.timers[id] = timer;
return timer;
};
/*
* Replacement for setTimeout
* Similar signature:
* setTimeout ( function, delay [obj,arg1...] )
*/
PosfScheduler.prototype.setTimeout = function () {
var args = Array.prototype.slice.call(arguments);
return this.addTimer.apply.bind( this.addTimer, this, [false].concat(args) ).apply();
};
/*
* Replacement for setInterval - Untested for now.
* Signature:
* setInterval ( function, delay [obj,arg1...] )
*/
PosfScheduler.prototype.setInterval = function () {
var args = Array.prototype.slice.call(arguments);
return this.addTimer.apply.bind( this.addTimer, this, [true].concat(args) ).apply();
};
PosfScheduler.prototype.cancelTimeout = function ( timer ) {
delete this.timers[timer.id];
};
/*
* Don't want to leave all these schedulers hogging the javascript thread.
*/
PosfScheduler.prototype.shutdown = function () {
delete this;
};
return PosfScheduler;
})();
var scheduler = new PosfScheduler();
var initTime = Date.now();
var timer1 = scheduler.setTimeout ( function ( init ) {
console.log ('executing function1 (should appear) after ' + String ( Date.now() - init ) + 'ms!' );
}, 200, null, initTime );
var timer2 = scheduler.setTimeout ( function ( init ) {
console.log ('executing function2 afte: ' + String ( Date.now() - init ) + 'ms!' );
}, 300, null, initTime );
var timer3 = scheduler.setTimeout ( function ( init ) {
console.log ('executing function3 (should not appear) after ' + String ( Date.now() - init ) + 'ms!' );
}, 1000, null, initTime );
var timer4 = scheduler.setTimeout ( function ( init, sched, timer ) {
console.log ('cancelling timer3 after ' + String ( Date.now() - init ) + 'ms!' );
sched.cancelTimeout ( timer3 );
}, 500, null, initTime, scheduler, timer3 );
var timer5 = scheduler.setInterval ( function ( init, sched, timer ) {
console.log ('periodic after ' + String ( Date.now() - init ) + 'ms!' );
}, 400, null, initTime, scheduler, timer3 );
var timer6 = scheduler.setTimeout ( function ( init, sched, timer ) {
console.log ('cancelling periodic after ' + String ( Date.now() - init ) + 'ms!' );
sched.cancelTimeout ( timer5 );
}, 900, null, initTime, scheduler, timer5 );