概述
理解 Promise
的执行顺序时,需要牢记以下两点:
-
微任务与宏任务的优先级:
- 微任务:
Promise.then()
、catch
、finally
是微任务。 - 宏任务:
setTimeout
、setInterval
是宏任务。 - 微任务的优先级高于宏任务:在一次事件循环中,先清空所有的微任务队列,再执行下一个宏任务。
- 微任务:
-
Promise 是基于微任务实现的:
- 当一个
Promise
的状态变为resolved
或rejected
时,它的.then()
回调会被加入微任务队列,等待当前任务(包括微任务)完成后执行。
- 当一个
示例代码分析
代码分析
以下代码可以帮助理解 Promise
和 setTimeout
的执行顺序:
console.log("script start");
setTimeout(() => {
console.log("setTimeout 1");
}, 0);
Promise.resolve()
.then(() => {
console.log("promise 1");
return Promise.resolve().then(() => {
console.log("promise 2");
});
})
.then(() => {
console.log("promise 3");
});
setTimeout(() => {
console.log("setTimeout 2");
}, 0);
console.log("script end");
执行过程解析
-
同步任务:立即执行
console.log("script start")
输出"script start"
.setTimeout
的两个回调函数被放入 宏任务队列,等待事件循环调度。Promise.resolve()
被调用,then()
的回调被放入 微任务队列。
输出结果:
script start script end
-
主线程执行完同步任务后,开始执行微任务队列
- 微任务队列的顺序如下:
- 第一个
.then()
输出"promise 1"
并返回一个新的Promise
。 - 新的
Promise.then()
输出"promise 2"
。 - 第二个
.then()
输出"promise 3"
。
- 第一个
输出结果:
promise 1 promise 2 promise 3
- 微任务队列的顺序如下:
-
清空微任务队列后,开始执行宏任务队列
- 宏任务队列的两个
setTimeout
回调依次执行,输出"setTimeout 1"
和"setTimeout 2"
。
输出结果:
setTimeout 1 setTimeout 2
- 宏任务队列的两个
最终输出
综合以上,代码的输出顺序为:
script start
script end
promise 1
promise 2
promise 3
setTimeout 1
setTimeout 2
总结
- 同步任务优先执行,输出
script start
和script end
。 - 微任务队列优先于宏任务队列。
Promise.then()
的回调会依次进入微任务队列。setTimeout
的回调进入宏任务队列,最后执行。
(拓展)问题补充
如果第一个then不是返回return promise,而是直接执行一个Promise.resolve().then(() => { console.log("promise 2"); });
结果是不是
会变成 promise 1
→ promise 3
→ promise 2
。结果是的。
为什么会这样?
当 Promise.resolve().then()
不通过 return
将内部的 Promise
链接到外部 then
时,promise 2
的执行不再是当前链的一部分,它会被单独添加到 微任务队列的末尾,导致执行顺序的变化。
示例代码
以下是修改后的代码:
console.log("script start");
Promise.resolve()
.then(() => {
console.log("promise 1");
Promise.resolve().then(() => {
console.log("promise 2");
});
})
.then(() => {
console.log("promise 3");
});
console.log("script end");
执行过程解析
-
同步任务
- 输出
"script start"
。 - 主线程继续,将第一个
Promise.then()
的回调加入 微任务队列。 - 输出
"script end"
。
当前输出:
script start script end
- 输出
-
微任务队列开始执行
- 执行第一个
.then()
,输出"promise 1"
。
在此回调中,一个新的微任务(promise 2
的回调)被加入 微任务队列末尾。 - 执行第二个
.then()
的回调,输出"promise 3"
。
当前输出:
promise 1 promise 3
- 执行第一个
-
微任务队列剩余任务
- 微任务队列中剩余的任务是
promise 2
的回调,输出"promise 2"
。
最终输出:
promise 2
- 微任务队列中剩余的任务是
总输出结果
综合以上,完整的输出顺序是:
script start
script end
promise 1
promise 3
promise 2
关键点解析
-
链式调用和微任务队列
当你不通过return
将一个新的Promise
链接到当前then
,它的回调会独立加入 微任务队列的末尾,而不是成为当前链的一部分。 -
对比:返回
Promise
如果return Promise.resolve().then(...)
,那么promise 2
的执行会成为当前链的一部分,顺序为promise 1 → promise 2 → promise 3
。
改变的核心代码
-
独立的微任务:
Promise.resolve().then(() => { console.log("promise 2"); });
-
作为链的一部分:
return Promise.resolve().then(() => { console.log("promise 2"); });
两种写法的区别在于是否将新的 Promise
加入当前链。