带有阻塞代码的 setTimeout 行为

IT技术 javascript settimeout
2021-02-19 10:29:44

这是我的测试代码(在这里小提琴):

console.log('Before wait');
setTimeout(function () { console.log('Yo!'); }, 1000);
var start = Date.now();
while (Date.now() < start + 3000) {}
console.log('After wait');

这是 Chrome 中事件的时间线:

  • 时间 0 秒:打印“等待前”
  • 时间 3 秒:打印“After wait”,然后在“Yo!”之后立即打印

这种行为是否符合规范?为什么不是

  • 时间 0 秒:打印“等待前”
  • 时间 3 秒:打印“等待后”
  • 时间 4 秒:打印“Yo!”

?

4个回答

JavaScript 是单线程的。如果某个代码块使用执行线程,则不能执行其他代码。这意味着您的setTimeout()调用必须等到主执行(忙等待while循环)完成。

这是发生的事情:您安排setTimeout()在一秒后执行,然后阻塞主线程 3 秒。这意味着当您的繁忙循环完成时,超时已经晚了 2 秒 - 并且 JS 引擎试图通过尽快调用您的超时来跟上 - 即立即。

其实这个:

while (Date.now() < start + 3000) {}

是 JavaScript 中最糟糕的事情之一。您持有 JavaScript 执行线程 3 秒钟,并且无法执行其他事件/回调。通常浏览器会在这段时间内“冻结”。

我认为这也是一个很好的答案——它确实准确地描述了事件的顺序,但没有直接回答为什么“哟!”的问题。4 秒后不打印,而不是 3 或 1 秒。对我来说,它似乎在打印“哟!” 4 秒后是不合逻辑的。
2021-04-20 10:29:44
@Randomblue:你是对的,我从你的问题中错过了这一点。更新了我的答案。
2021-05-18 10:29:44
JavaScript 是单线程的这一事实解释了为什么“哟!” “等待之后”之后打印,仅此而已。
2021-05-20 10:29:44
@Randomblue,这是最好的答案(到目前为止),尽管赞成票较少。奇怪的。
2021-05-20 10:29:44

的延迟与setTimeout调用时的确切时间点有关。当您还在忙着等待时,它就会过期。因此它将在控制返回事件循环的下一个时刻执行。

编辑:

规范在这一点上有点含糊,但我想这是有意且唯一直接的解释:

setTimeout(函数,毫秒)

此方法在经过指定的毫秒数后调用该函数一次,直到被调用 clearTimeout 取消。这些方法会返回一个 timerID,该 ID 可用于后续调用 clearTimeout 以取消间隔。

上面编辑 - 添加了规范的摘录。
2021-04-23 10:29:44
你有支持这个的规范吗?
2021-04-26 10:29:44
对我来说很明显,传递给“setTimeout”的函数将在尽可能接近指定的毫秒数时执行,相对于调用函数时 - 在这个问题中,你似乎认为它在当前执行后 1 秒执行线程退出 - 但这对我来说似乎根本不合逻辑。
2021-05-01 10:29:44

当您在 setTimeout 调用后运行忙等待循环时,您不会为“哟!”留出时间。打印出来,因为 Javascript 运行时正忙于您的循环(实际上,由于循环条件的继续评估,空语句也使其忙碌)。

您应该始终避免这种忙等待循环,因为在该循环完成之前,在该窗口中不能调用或运行其他任何东西。

“等待之后”将永远获胜。
2021-04-23 10:29:44
感谢您的更正,我会更新答案。无论如何,我认为其余的可能是真的。这甚至与 javascript 无关,而是与相当多的环境有关。
2021-04-25 10:29:44

不确定它是否可以帮助您,但此问题已在以下位置进行了解释:https : //developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#Run-to-completion

出于这个原因,第二个参数表示最短时间——而不是保证时间。