阅读本文章,默认你已经学会 Promise ,宏和微任务等概念。不会就赶紧去学吧。
先举个栗子
对于一处于pending状态的Promise对象p,内部状态的resolve,会让p.then(fn)中的fn加入微任务队列
//code 1
let p = new Promise(function f1(resolve) {
setTimeout(function f2(){
resolve(2)
console.log(1)
}, 1000)
})
p.then(function f3(v) { console.log(v) })
在上面的代码中,f1 是同步执行的代码,在执行时创建一个定时器( f2 会加入定时器),之后执行 p.then ,此时 f3 并没有立即加入微任务队列。1秒后f2 执行时,运行 resolve(2),此时才触发 f3 加入微任务队列。
- 运行同步代码 f1 ,创建定时器,初始化 p.then
- 1秒后运行 f2 ,f2 加入宏任务队列。此时宏任务队列:[f2],微任务队列:[].
- 先扫描微任务队列(为空),再扫描宏任务队列(拿出一个任务 f2 ),运行 f2 。遇到 resolve ,把 p.then 的 f3 移入微任务队列,之后输出1。此时宏队列:[],微队列[f3]
- 执行完宏任务,再次扫描微任务队列,依次拿出并执行全部任务,执行 f3 输出2。
最后的结果是1秒后输出1,2。
再举个栗子
//code 2
let p1 = new Promise(function f1(resolve1) {
setTimeout(resolve1)
})
let p2 = p1.then(function f2(v) { console.log(2) })
let p3 = p2.then(function f3(v) { console.log(3) })
let p11 = new Promise(function f11(resolve2) {
setTimeout(resolve2)
})
let p22 = p11.then(function f22(v) { console.log(22) })
let p33 = p22.then(function f33(v) { console.log(33) })
对于 Promise 我们需要知道,链式调用 .then 之后会返回一个新的 Promise 对象。以上代码的执行流程是
- 先执行同步代码。运行 f1,立即得到一个 Pending 状态的 Promise 对象 p1 ( f1 里面的 resolve1 函数被加入宏任务,还没开始执行)。运行 p1.then 得到一个 Pending 状态的 Promise 对象 p2 。 ......,同理你得到一个 pending 状态的 Promise 对象 p33。此时宏队列里有 [resolve1,resolve2]
- 因为微队列目前为空,所以扫描宏队列,拿出 resolve1 运行,导致 p1 被 resolve(从 pending 变成了 fullfilled),从而导致 p1.then(f2)被加入了微队列。此时宏任务队列[resolve2],微任务队列[f2]。
- 宏任务执行完,扫描微任务队列,执行 f2 ,输出2。p3 因为 p2 的状态 pending 变化成 fullfilled,导致 p2.then(f3)中的 f3 加入微任务队列。此时宏任务队列[resolve2],微任务队列[f3]。微任务队列不为空,拿出 f3,执行 f3 ,输出3,此时 p3 变为 fullfilled 状态,微任务队列为空。
- 扫描下一个宏任务,拿出 resolve2 ,运行,导致 p11 被 resolve,导致 p11.then(f22)中的 f22 被加入微任务队列。此时宏队列[],微任务队列[f22]
- 扫描全部的微任务,执行 f22,输出 22 。f22 运行结束,触发 p22 的状态从 pending 变成 fullfilled ,导致 p22.then 加入微任务队列。此时宏队列[],微队列[f33]。此时宏队列[],微队列[f33]。微任务队列还未扫描完,拿出f33,运行输出33,此时p33变成fulfilled状态。微队列为[]。
最终输出顺序为 2、3、22、33。以上代码等价于常见链式写法
new Promise( resolve => setTimeout(resolve) )
.then( v => console.log(2) )
.then( v => console.log(3) )
new Promise( resolve => setTimeout(resolve) )
.then( v => console.log(22) )
.then( v => console.log(33) )
再来个栗子
对于一处于fulfilled状态的Promise对象p,p.then(fn)会立即让fn加入微任务队列
// code 3
let p1 = Promise.resolve(1)
let p2 = p1.then(function f2() {
console.log(2)
})
let p3 = p2.then(function f3() {
console.log(3)
})
let p11 = new Promise(function f11(resolve) {
resolve(11)
})
let p22 = p11.then(function f22() {
console.log(22)
})
let p33 = p22.then(function f33() {
console.log(33)
})
看起来和 code2 差不多,但是执行起来会得到不同的结果。分析一下具体的流程。
- p1 = Promise.resolve()。创建了一个 Promise 对象 p1,其内部状态为 fullfilled。
- p2 = p1.then(f2)。对于一处于 fullfilled 状态的 Promise 对象 p1,会立即让 f2 加入微任务队列( f2 并未执行),创建的 p2 是 pending。此刻微队列:[f2]
- p3 = p2.then(f3).对于一处于 pending 状态的 Promise 对象 p2 ,p2的状态 resolve 才会让 f3 加入微队列。
- p11 创建了一个 Promise 对象,内部状态为 fullfilled。
- p22 = p11.then(f22).对于一个处于 fullfilled 状态的 Promise 对象 p11,会立即让 f22 加入微任务队列,创建的 p22 是 pending 的状态。此刻的微队列:[ f2 , f22 ]
- 对于一个处于 pending 状态的 p22,f33 目前不会加入微队列中。
- 扫描微任务队列
- 拿出 f2 ,运行输出 2.。输出完毕,状态变为 fullfilled,将 f3 加入微队列中。此时微队列:[ f22, f3 ]
- 拿出 f22 运行输出 22。f22 执行完时(函数结束或者遇到 turn),p22 被 resolve,触发 f33 加入微队列。此刻微队列:[ f3, f33]
- 拿出 f3,运行输出 3。f3 执行完时, p3 变成 fullfilled 状态。
- 拿出 f33 ,运行输出 33 。f33执行完,p33 变化成 fullfilled 状态。
最终输出结果为 2、22、3、33。以上代码等价于常见链式写法
// code 4
Promise.resolve(1)
.then(() => console.log(2))
.then(() => console.log(3))
new Promise(resolve => resolve())
.then(() => console.log(22))
.then(() => console.log(33))
到目前为止,你应该真正理解了宏任务、微任务、以及Promise。