You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaScript中Promise内setTimeout为何晚于外部setTimeout执行

核心原因

最终输出2,1的本质是:宏任务队列严格遵循先进先出的执行规则,任务的执行顺序只和它被注册进队列的时间先后有关,和回调写在微任务内部还是全局代码里没有任何关系。

复现代码

Promise.resolve().then(() => {
    setTimeout(() =>{
        console.log("1")
    }, 0)
})

setTimeout(() => {
    console.log("2")
}, 0)

完整执行流程拆解

按照JS事件循环的标准规则,代码运行过程可以拆为3个阶段:

  1. 第一轮宏任务:初始脚本同步执行
    整段脚本作为第一个宏任务启动,从上到下执行同步代码:
    • 运行到Promise.resolve().then(...)时,因为Promise已经是resolve的完成状态,传入then的回调会被加入当前轮次的微任务队列,不会立刻运行。
    • 继续往下运行到外层的setTimeout(..., 0),这行代码本身是同步执行的,执行后会立刻把打印2的回调注册到宏任务队列里,这时候宏任务队列里第一个待执行的任务就是打印2的回调。
      到这里所有同步代码运行完毕。
  2. 清空当前轮次全部微任务
    事件循环的固定规则是:每一轮宏任务执行结束后,必须把当前微任务队列里的所有任务全部执行完,才会开始调度下一轮宏任务。
    • 从微任务队列取出之前存入的then回调开始执行,运行到回调内部的setTimeout(..., 0)时,才会把打印1的回调注册到宏任务队列。这时候宏任务队列的顺序已经是[打印2的回调, 打印1的回调]——打印2的回调比打印1的回调早了整整一个微任务阶段注册,自然排在队列更靠前的位置。
    • 微任务队列所有任务执行完毕,没有剩余待运行的微任务。
  3. 后续宏任务轮次调度
    事件循环开始按先进先出的规则从宏任务队列取任务执行:
    • 先取出排在队首的打印2的回调运行,控制台输出2;该回调运行结束后检查微任务队列,没有新增微任务,继续调度下一个宏任务。
    • 取出队列里下一个打印1的回调运行,控制台输出1。

常见认知误区

你之前预期输出1,2,是错误理解了微任务的优先级规则:

  • 微任务优先级高于宏任务,仅指「每轮宏任务执行完后,要优先清空所有微任务,才会处理下一个宏任务」,从来不是说写在微任务里的宏任务可以获得插队权限,跑到已经在队列里等待的其他宏任务前面。
  • 所有setTimeout的回调都不会在写代码的时候就自动进入宏任务队列,只有真正运行到setTimeout()这行语句时,对应的回调才会被推入宏任务队列排队,注册时间晚的任务自然要等前面的任务执行完才会运行。

内容的提问来源于stack exchange,提问作者GLHF

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 00:27:22