一、前言
在 JavaScript 的异步编程中,事件循环(Event Loop) 决定了代码的执行顺序。Promise 和 setTimeout 作为常见的异步操作,很多人容易混淆它们的执行顺序。
今天,我们通过一段代码深入解析 JavaScript 事件循环的执行机制,并彻底搞懂 同步任务、微任务、宏任务 的调度规则!
二、代码示例:Promise + setTimeout,谁先执行?
ts
setTimeout(() => {
console.log(1)
}, 0)
const p = new Promise((resolve, reject) => {
console.log(2)
resolve(3)
Promise.resolve(4).then(console.log)
console.log(5)
}).then(console.log)
console.log(6)
在不运行代码的情况下,你能猜出它的输出顺序吗? (建议先思考一下,再往下看解析 🤔)
三、代码执行流程详解
3.1、执行同步代码
JavaScript 会先执行所有 同步任务 ,然后再处理 异步任务(微任务 > 宏任务)
setTimeout(宏任务) 只是被注册,不会立即执行,而是放入 宏任务队列Promise构造函数内的代码 是同步执行的 :console.log(2)同步执行 ,输出2resolve(3)只是改变 Promise 状态 ,但.then(console.log)不会立刻执行Promise.resolve(4).then(console.log)创建了一个微任务 ,加入 微任务队列console.log(5)同步执行 ,输出5
console.log(6)同步执行 ,输出6
3.2、执行微任务队列(Microtask Queue)
所有 同步代码执行完毕 后,JavaScript 进入 微任务阶段,开始执行微任务队列中的任务。
Promise.resolve(4).then(console.log)先执行,输出4resolve(3).then(console.log)执行,输出3
3.3、执行宏任务队列(Macrotask Queue)
所有 微任务执行完毕 后,JavaScript 进入 宏任务阶段 ,执行 任务队列中的宏任务
setTimeout任务被执行,输出1
四、最终执行顺序
ts
setTimeout(() => {
console.log(1)
}, 0) // 宏任务(MacroTask),延迟执行,放入任务队列
const p = new Promise((resolve, reject) => {
console.log(2) // 立即执行(同步代码)
resolve(3) // 将 `3` 作为 Promise 的解决值
Promise.resolve(4).then(console.log) // 微任务,then 立即进入微任务队列
console.log(5) // 继续执行同步代码
}).then(console.log) // `resolve(3)` 触发的 then,进入微任务队列
console.log(6) // 继续执行同步代码
- 2 // Promise 内同步执行
- 5 // Promise 内同步执行
- 6 // 主线程同步执行
- 4 // 微任务:Promise.resolve(4).then()
- 3 // 微任务:resolve(3).then()
- 1 // 宏任务:setTimeout
五、JavaScript 事件循环(Event Loop)核心规则
- 先执行所有同步代码 ,这些代码会直接进入 调用栈(Call Stack) 立即执行
- 遇到异步任务时
- 微任务(Microtask) (Promise.then、MutationObserver、queueMicrotask)进入 微任务队列
- 宏任务(Macrotask) (setTimeout、setInterval、setImmediate、I/O 操作、UI 渲染)进入 宏任务队列
- 同步代码执行完后
- 先清空微任务队列(按照先进先出的顺序执行)
- 再执行宏任务队列中的任务(setTimeout 等)
- 进入下一个事件循环,重复以上过程
六、总结
- 同步代码最先执行
- Promise.then(微任务)优先于 setTimeout(宏任务)
- 微任务的执行顺序遵循先进先出(FIFO)
- JavaScript 采用单线程机制,但通过事件循环处理异步任务
七、思考题 🤔
阅读完这篇文章后,你能正确判断下面代码的输出顺序吗?
ts
console.log('A')
setTimeout(() => {
console.log('B')
}, 0)
Promise.resolve()
.then(() => {
console.log('C')
})
.then(() => {
console.log('D')
})
console.log('E')
在评论区留下你的答案,并告诉我你的思考过程吧!🚀
希望这篇文章能帮你彻底理解 JavaScript 事件循环,欢迎点赞 👍、收藏 ⭐、留言 💬 交流!