含异步操作的JavaScript程序输出不符预期的原因排查
你的JavaScript代码执行顺序不符合预期?来梳理下事件循环的细节
先把你提供的代码贴出来方便分析:
setTimeout(function() { console.log("Callbcack"); }, 0); new Promise((resolve, reject) => { console.log("Inside promise"); let ms = 5000 + new Date().getTime(); while (new Date() < ms) {} resolve("Promise is resolved"); }).then((data) => {console.log(data)}).catch((err) => { console.log(err); }) var ms = 2000 + new Date().getTime(); while (new Date() < ms) {}
你的预期和实际不符,核心是忽略了三个关键的事件循环和JavaScript执行机制细节:
1. Promise的executor函数是同步执行的
你误以为Promise的resolve是异步触发的,但实际上new Promise()的回调(executor)会在创建Promise的当下立即同步执行。所以代码执行到这里时,会先打印Inside promise,然后直接进入5秒的while循环——这会完全阻塞主线程,在这5秒里,浏览器什么都做不了:定时器的回调没法被触发,UI没法更新,所有异步任务都得等主线程空闲。
2. 事件循环的优先级:微任务 > 宏任务
JavaScript的事件循环执行顺序是固定的:
- 先执行完当前所有同步任务(也就是主线程上的代码)
- 然后清空微任务队列(比如Promise的then/catch/finally回调、MutationObserver回调等)
- 最后才会从宏任务队列(比如setTimeout/setInterval回调、DOM事件回调等)中取出一个任务执行
在你的代码里,当executor执行完resolve("Promise is resolved")后,Promise的then回调会被加入到微任务队列。而setTimeout的回调是被加入到宏任务队列。当所有同步代码(两个while循环,总共7秒)执行完后,主线程会先处理微任务队列,所以先打印Promise is resolved,之后才会处理宏任务队列里的setTimeout回调,打印Callbcack。
3. setTimeout的"0延迟"不是真的立即执行
浏览器对setTimeout有最小延迟限制(一般是4ms),但更关键的是:即使你设置0延迟,回调也得等主线程空闲后才能执行。在你的代码里,主线程被两个while循环阻塞了7秒,所以setTimeout的回调根本不可能在2秒后触发——它得等这7秒的同步代码全部跑完,才会有机会执行。
完整的执行时序梳理
- 执行
setTimeout:将回调加入宏任务队列,标记"最早0ms后执行",但此时主线程忙,暂时无法处理。 - 执行
new Promise:立即同步执行executor函数:- 打印
Inside promise - 执行5秒的while循环,阻塞主线程
- 调用
resolve,将then回调加入微任务队列
- 打印
- 执行后面的2秒while循环,继续阻塞主线程
- 所有同步任务执行完毕,主线程开始处理微任务队列:
- 执行Promise的then回调,打印
Promise is resolved
- 执行Promise的then回调,打印
- 微任务队列清空,主线程处理宏任务队列:
- 执行setTimeout的回调,打印
Callbcack
- 执行setTimeout的回调,打印
内容的提问来源于stack exchange,提问作者sv2311
相关产品推荐
相关产品推荐

