Firefox中使用setTimeout在alert之后执行的代码顺序错误

8
在任何浏览器中运行以下代码(尝试多次):

console.log('processing: task #1');
setTimeout(function(){
  console.log('processing: task #3');
},0);
alert('See console logs');
console.log('processing: task #2');

在我看来,上面的代码将会导致控制台输出如下内容:
"processing: task #1"
"processing: task #2"
"processing: task #3"

但在Firefox(v38.0.1)中,它会产生以下输出:

"processing: task #1"
"processing: task #3"
"processing: task #2"

请解释为什么Firefox以这种方式执行操作。不知道这是否是错误或者Firefox自己的标准。 你可以在这里查看实时演示:http://jsbin.com/cijonu

我在Linux上的Firefox 38.0.1也无法重现这个问题。 - Florian Margaine
Firefox已经在警告框内插入了调整大小事件,但是超时?我想答案就是不要使用警告框来确保同步执行。 - John Dvorak
@Hacketo 是的。这个顺序表明要么超时被同步执行(严重的错误),要么在显示警报时执行(仍然是非常糟糕的错误,但不那么出乎意料)。 - John Dvorak
@JanDvorak 我猜我会期望这种行为,因为setTimeout中的函数将在下一个执行栈上触发,而alert可能会阻塞执行并推迟下一行代码(因此再次将这些行附加到下一个执行栈上)。 (我从不使用alert - Hacketo
是的,如果alert暂停同步执行(技术上并不需要这样做),则还需要暂停事件循环。 - John Dvorak
显示剩余12条评论
2个回答

6
这是一个bug。HTML5规范不允许事件处理程序在显示警报时触发:

第6.1.4节定义了事件循环。

在6.1.4.2中,此部分还有助于提供关于事件触发顺序(事件循环算法的第1点)和文档在事件处理程序返回后呈现(点4.3)的某些保证。

第6.4节定义了setTimeout的计时器初始化步骤:

...14. 将任务task排队。

本节的第12点也提供了有关多个超时相对顺序的保证-稍后发布的超时可能不会在较早发布的超时之前触发,除非它具有更短的超时

第6.5节定义了alert行为。

  1. 可选地,中止这些步骤。 (例如,用户代理可能会给用户忽略所有警报的选项,并且因此在调用该方法时在此步骤中中止)。

  2. 向用户显示给定的message

  3. 可选地,在等待用户确认消息时暂停

(注意:对于confirmprompt,暂停是必需的,因为它们基于用户操作返回)

暂停的规范说明如下:

由于历史原因,本规范中的某些算法要求用户代理在运行任务时暂停,直到达到条件目标为止。这意味着执行以下步骤:
...
3. 等待条件goal满足。当用户代理有暂停的任务时,相应的事件循环不得运行进一步的任务,并且当前正在运行的任务中的任何脚本都必须阻塞。用户代理在暂停时应保持对用户输入的响应性,但是由于事件循环将不会做任何事情,因此响应能力将降低。

(强调我的)

请注意,如果alert没有暂停,控制台日志仍然必须按顺序记录:先完成同步执行(日志#2),然后超时触发(日志#3),然后用户关闭警报。正如页面JavaScript所观察到的那样,行为就像用户立即关闭了警报框一样。

很好地解释了为什么这是一个错误。 - Greg

3

我在Bugzilla上报告了一个错误,链接在这里:https://bugzilla.mozilla.org/show_bug.cgi?id=1167575。但似乎没有人关注这个问题,所以我想知道它是否真的是一个错误。 - Greg
当您的错误报告被确认有效后,我将接受您的答案。 - Greg
@Greg 这是一个 bug。规范要求 alert 要么不暂停同步执行,要么同时暂停事件循环。 - John Dvorak
@JanDvorak 是的,我在看到 OP 也报告了这个错误后才这样做的。 - Florian Margaine

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接