为何含0ms setTimeout的宏任务队列优先级看似高于微任务队列?
问题
我原本认为,当宏任务队列和微任务队列中都有就绪待执行的任务时,微任务拥有更高的优先级。我通过让两个队列都填满任务的方式进行代码测试,验证该规则是否成立,但发现0ms的setTimeout并非如此——它总是先执行。不过如果我添加Promise.resolve('Promise').then(displayData)这段代码,该微任务会先于0ms的setTimeout执行。以下是测试代码及输出,请求解释该现象:
// after 3000ms: // task queue:printFoo(0ms), printHi(1ms), PrintHello(2ms) // microtask queue: displayData (about 50ms) function printHello() { console.log("hello"); } function printHi() { console.log("hi"); } function printFoo() { console.log("foo"); } function displayData(data) { console.log("data", data); } function sleep(miliseconds) { var currentTime = new Date().getTime(); while (currentTime + miliseconds >= new Date().getTime()) {} } setTimeout(printHi, 1); setTimeout(printHello, 2); setTimeout(printFoo, 0); fetch("https://jsonplaceholder.typicode.com/todos/1").then(displayData); // ~~ 50ms // Promise.resolve('Promise').then(displayData); sleep(3000);
输出:
foo data from fetch hi hello
原因分析
- 微任务优先级高于宏任务的核心前提是:微任务已经被加入微任务队列,和宏任务处于同等“就绪待执行”状态。
- 你的测试代码里,
fetch的回调依赖网络响应:虽然sleep(3000)阻塞主线程的过程中,网络请求可能已经完成,但主线程被阻塞时,浏览器无法处理网络响应的回调逻辑,所以displayData直到sleep结束后才会被加入微任务队列。 - 而三个
setTimeout的回调在各自延迟时间(0ms、1ms、2ms)到期后,会被准时加入宏任务队列——哪怕主线程被阻塞,宏任务队列依然会接收这些任务,只是暂时无法执行。 - 当
sleep结束,主线程处理任务的流程是:- 先从宏任务队列取出最早就绪的
printFoo(0ms延迟的setTimeout)执行,输出foo。 - 执行完这个宏任务后,主线程会强制清空所有微任务队列——这时候浏览器才处理完
fetch的响应,把displayData加入了队列,所以立刻执行它,输出data from fetch。 - 微任务全部执行完毕后,再回到宏任务队列,依次执行
printHi和printHello,输出hi和hello。
- 先从宏任务队列取出最早就绪的
- 换成
Promise.resolve('Promise').then(displayData)时,这个微任务会在代码执行到该行时直接被加入微任务队列,不需要等待任何异步操作。当sleep结束后,主线程会先清空微任务队列(执行displayData),再去处理宏任务队列里的setTimeout回调,所以微任务会先执行。
内容的提问来源于stack exchange,提问作者Loc To
相关产品推荐
相关产品推荐

