Promise和setTimeout都是处理异步操作的重要工具,那么它们实际的执行顺序是怎么样的呢?
先来看下面的案例:
console.log(1)
setTimeout(()=>{
console.log(2)
})
new Promise(resolve => {
resolve()
console.log(3)
}).then(()=>{
console.log(4)
})
console.log(5)
// 打印结果为13542
执行原因
事件循环与任务队列
以上案例结果打印的原因,前提需要了解一下JavaScript的事件循环和任务队列,JavaScript引擎中有一个事件循环,它会不断地从任务队列中取出任务并执行。这些任务可以是同步任务,也可以是异步任务。同步任务会立即执行,而异步任务则会在满足一定条件后(如请求返回结果、定时器时间到达等)被添加到任务队列中等待执行。
js代码在执行的时候,会先执行同步代码,遇到异步宏任务则将异步宏任务放入宏任务队列中,遇到异步微任务则将异步微任务放入微任务队列中,当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,微任务执行完毕后,再将异步宏任务从队列中调入主线程执行,一直循环至所有的任务执行完毕(完成一次事件循环EventLoop)。
宏任务与微任务
现在我们来分析Promise和setTimeout的执行顺序。当我们在代码中同时使用Promise和setTimeout时,由于Promise是微任务(microtask),而setTimeout是宏任务(macrotask),根据JavaScript的事件循环机制,微任务会优先于宏任务执行。因此,Promise里的代码通常会比setTimeout先执行。
常见宏任务,包括以下几种:
- 主代码块:JavaScript中的主线程代码;
- 定时器任务:setTimeout和setInterval;
- I/O操作:例如文件读写、网络请求;
- UI渲染:浏览器需要重排或者重绘的任务;
- 用户交互事件:例如点击、滚动、输入等用户操作触发的事件。
常见微任务,包括以下几种:
- Promise回调函数:promise的处理程序(then、catch、finally);
- MutationObserver:监听DOM变化的任务;
- process.nextTick(node.js环境下):当前“执行栈”结束后立即执行的任务;
这里需要注意的是new Promise实例对象中的代码时同步执行的,会立即执行。
再来看下面的例子:
console.log(1)
setTimeout(() => {
console.log(2)
})
new Promise((resolve, reject) => {
setTimeout(() => {
console.log(7)
})
resolve()
// reject()
console.log(3)
new Promise((resolve) => {
setTimeout(() => {
console.log('a')
})
resolve()
console.log('b')
})
.then(() => {
setTimeout(() => {
console.log('c')
})
console.log('d')
})
.catch(() => {
console.log('e')
})
})
.then(() => {
setTimeout(() => {
console.log(6)
})
console.log(4)
})
.catch(() => {
console.log(8)
})
console.log(5)
// 打印结果为13b5d427ac6
console.log(1)
setTimeout(() => {
console.log(2)
})
new Promise((resolve, reject) => {
setTimeout(() => {
console.log(7)
})
// resolve()
reject()
console.log(3)
new Promise((resolve) => {
setTimeout(() => {
console.log('a')
})
resolve()
console.log('b')
})
.then(() => {
setTimeout(() => {
console.log('c')
})
console.log('d')
})
.catch(() => {
console.log('e')
})
})
.then(() => {
setTimeout(() => {
console.log(6)
})
console.log(4)
})
.catch(() => {
console.log(8)
})
console.log(5)
// 打印结果为 13b5d827ac
执行顺序,如下:
从上到下依次执行,遇到setTimeout,就放到宏任务队列中,遇到promise,实例对象中的代码遇到会立刻执行,如果其中也有setTimeout,也放到宏任务队列中,遇到.then、.catch,放到异步队列中,同步执行完后,开始执行微任务队列中的任务,执行完微任务,再执行宏任务队列中的任务。