为何调用栈空时事件循环未处理回调队列?代码执行顺序疑问
为什么sync2()会先于Callback Queue中的setTimeout回调执行?
已知Event Loop会在Call Stack为空时从Callback Queue取出首个回调执行。基于此前提,为何下述代码中sync2()会先于Callback Queue中等待的setTimeout回调执行?
const baseData = [1, 2, 3, 4, 5, 6, 100]; function asyncRun(arr, fn) { arr.forEach((v, i) => { setTimeout(() => fn(i), 0); }); }; function sync1() { baseData.forEach((v, i) => { console.log("sync1 -", i); }); } function sync2() { baseData.forEach((v, i) => { console.log("sync2 -", i); }); } asyncRun(baseData, (idx) => console.log("async -", idx)); sync1(); for (let i = 0; i < 5000000000; i++) {} // 约5秒,确保setTimeout回调已准备好进入调用栈 // 此时Call Stack为空,Callback Queue中有等待的回调 sync2();
实际输出:
sync1 - 0 sync1 - 1 sync1 - 2 sync1 - 3 sync1 - 4 sync1 - 5 sync1 - 6 sync2 - 0 sync2 - 1 sync2 - 2 sync2 - 3 sync2 - 4 sync2 - 5 sync2 - 6 async - 0 async - 1 async - 2 async - 3 async - 4 async - 5 async - 6
预期输出:
sync1 - 0 sync1 - 1 sync1 - 2 sync1 - 3 sync1 - 4 sync1 - 5 sync1 - 6 async - 0 async - 1 async - 2 async - 3 async - 4 async - 5 async - 6 sync2 - 0 sync2 - 1 sync2 - 2 sync2 - 3 sync2 - 4 sync2 - 5 sync2 - 6
问题解析
你误解了Event Loop的触发时机——它不是在某一段同步代码执行完就立刻去轮询回调队列,而是要等整个当前执行栈完全清空之后才会启动。
具体执行流程拆解:
- 调用
asyncRun后,所有setTimeout回调会在定时器到期(这里因大循环耗时足够,肯定已到期)后被放入回调队列等待。 - 同步执行
sync1(),打印所有sync1相关日志。 - 执行耗时的for循环,这段代码跑完后,执行栈并没有真正清空——因为当前主执行上下文里还有
sync2()这个同步任务尚未执行,它属于当前要按顺序完成的同步代码序列的一部分。 - 所以接下来会优先执行
sync2(),完成所有同步打印;只有当sync2()执行完毕,整个主执行上下文的所有同步代码都跑完,执行栈彻底为空时,Event Loop才会去回调队列中取出setTimeout的回调执行。
核心逻辑:同步任务是按书写顺序连续执行到底的,中间不会插入异步回调,哪怕异步回调已经在队列中等待。只有当所有同步任务执行完毕、执行栈完全清空,Event Loop才会处理回调队列中的任务。
内容的提问来源于stack exchange,提问作者Kakamotobi
相关产品推荐
相关产品推荐

