宏任务与微任务决定执行顺序:同步代码先执行,宏任务如setTimeout后于微任务如promise.then执行。事件循环每次执行一个宏任务后立即清空微任务队列。例如,Promise初始化和then回调中,同步输出“Promise初始化”,接着“同步代码结束”,之后处理微任务输出“Promise then执行”,最后执行下一个宏任务“setTimeout执行”。即使在setTimeout内创建Promise,其then仍作为微任务在该宏任务结束后立即执行,而非等待下一宏任务。async/await也基于微任务机制,await后的代码被包装成Promise.then,延迟到微任务阶段执行。因此,正确理解宏任务与微任务的优先级和执行时机是掌握JavaScript异步行为的关键。

在 JavaScript 中,异步编程离不开事件循环(Event Loop)机制,而微任务队列是其中的关键部分。很多人对 Promise 和 setTimeout 的执行顺序感到困惑,其实只要理解了宏任务与微任务的区别,这个问题就迎刃而解。
宏任务与微任务的基本概念
JavaScript 的任务分为两类:
- 宏任务(macrotask):包括整体代码块、setTimeout、setInterval、I/O、ui 渲染等。
 - 微任务(Microtask):包括 Promise.then/catch/finally、MutationObserver、queueMicrotask 等。
 
事件循环每次从宏任务队列中取出一个任务执行,执行完毕后,会清空当前所有的微任务队列,然后再取下一个宏任务。
Promise 是微任务,setTimeout 是宏任务
这是理解执行顺序的核心。来看一个经典例子:
立即学习“Java免费学习笔记(深入)”;
let promise = new Promise(resolve => { console.log(‘Promise 初始化’); resolve(); }); promise.then(() => { console.log(‘Promise then 执行’); }); setTimeout(() => { console.log(‘setTimeout 执行’); }, 0); console.log(‘同步代码结束’);
输出结果为:
Promise 初始化 同步代码结束 Promise then 执行 setTimeout 执行
说明:
- “Promise 初始化” 是同步执行的。
 - “同步代码结束” 紧随其后。
 - 此时当前宏任务结束,开始处理微任务队列,执行 .then 回调。
 - 微任务清空后,进入下一个宏任务,执行 setTimeout 回调。
 
微任务在宏任务之间被集中执行
即使在 setTimeout 回调中创建 Promise,它的 .then 也会在该宏任务结束后立即执行:
setTimeout(() => { console.log(‘宏任务:setTimeout’); Promise.resolve().then(() => { console.log(‘微任务:setTimeout 内部的 Promise’); }); }, 0); Promise.resolve().then(() => { console.log(‘微任务:外部 Promise’); }); console.log(‘主流程结束’);
输出:
主流程结束 微任务:外部 Promise 宏任务:setTimeout 微任务:setTimeout 内部的 Promise
解释:
- 主流程是一个宏任务,执行完后先处理微任务队列,输出“外部 Promise”。
 - 然后进入下一个宏任务(setTimeout),输出“宏任务:setTimeout”。
 - 该宏任务执行完后,再检查是否有微任务——发现内部 Promise 的回调,于是执行它。
 
常见误区与注意事项
有些人认为 setTimeout(fn, 0) 就能立刻执行,但实际上它只是将任务推入下一个宏任务队列,必须等待所有微任务完成。
另一个误区是认为 async/await 不产生微任务。实际上,async 函数中的 await 后续代码会被包装成 Promise.then,也就是微任务。
async function async1() { console.log(‘async1 开始’); await async2(); console.log(‘async1 结束’); } async function async2() { console.log(‘async2’); } async1(); console.log(‘同步代码’);
输出:
async1 开始 async2 同步代码 async1 结束
因为 await 相当于把“async1 结束”放进微任务队列,等当前宏任务结束后才执行。
基本上就这些。掌握微任务和宏任务的执行节奏,就能准确预测 JavaScript 异步代码的行为。不复杂但容易忽略。