宏任务 与 微任务
浏览器中并不存在宏任务,宏任务(Macrotask)是 Node.js 发明的术语(Node.js里有六个阶段,每个阶段可以创建宏任务和微任务)。浏览器中只有任务(Task)和微任务(Microtask),一般入口都是任务,在任务的间隙插入微任务。
- 使用 script 标签、setTimeout 可以创建任务。
- 使用 Promise.then、window.queueMicrotask、MutationObserver、Proxy 可以创建微任务。
注意:微任务不能插微任务的队,只能插任务的队。
复杂题
已知结论:一个return Promise.resolve(...)等于两个.then
Promise.resolve()
.then(() => {
console.log(0); //f0
return Promise.resolve('4x'); //这里等于两个.then
})
.then((res) => {console.log(res)}) //f4
// 此处改写成三个then
/* .then(()=>{ //x
Promise.resolve(null).then(()=>{ //y
Promise.resolve(null).then(()=>{
console.log('4x')
})
})
}) */
Promise.resolve().then(() => {console.log(1);}) //f1
.then(() => {console.log(2);}) //f2
.then(() => {console.log(3);}) //f3
.then(() => {console.log(5);}) //f5
.then(() => {console.log(6);}) //f6
// 打印出 0, 1, 2, 3, 4x, 5, 6
then后面有连续的then:不是一次性将所有都入队,执行完第一个,得到执行结果,才放入后面的.then()
p.then(f1).then(f2,f3)
//先入队f1,执行f1,如果返回resolve值,则执行f2,否则返回reject值,执行f3
因此本题的执行顺序如下:
- f0入队,f1入队。微任务队列:[ f0,f1]
- 执行 f0 输出0,f4入队;执行 f1 输出1,f2入队。微任务队列: [f4,f2]
- 执行f4,x入队;执行 f2 输出2,f3入队。微任务队列: [x,f3]
- 执行 x ,y入队;执行 f3 输出3,f5入队。微任务队列:[y,f5]
- 执行y,输出4x;执行 f5 输出5,f6入队:微任务队列:[f6]
- 执行 f6 输出6:[空] 打印6
Promise 与 宏/微任务
- 同步:立刻执行的代码:
new Promise(f) - 异步:放入队列里的任务 宏:
setTimeout(f,t)微:Promise.then(f)
执行顺序
先执行同步代码,再执行所有微任务队列里的任务,直到执行完所有的微任务,再去执行一个宏任务,执行完看有没有新的微任务,有就执行,没有就继续执行下一个宏任务......
放入任务的时机
- 宏任务:
setTimeout(f,t)在 t 毫秒后将函数 f 放入任务队列,t=0或省略参数就立即放入。 - 微任务:
- 处于pending状态的Promise对象p,内部状态的resolve,会让
p.then(fn)中的fn加入微任务队列。 - 处于fulfilled状态的Promise对象p,
p.then(fn)会立即让fn加入微任务队列。
- 处于pending状态的Promise对象p,内部状态的resolve,会让
Promise 实例的状态变化
- 已知Promise的实例 p 有三种状态,pending、fulfilled、rejected
- 执行到
let p1=new Promise(...)和let p2=p1.then()时, p1、p2 就为pending状态。 - 当
let p1=new Promise(()=>{ ... resolve() })内部的resolve状态执行完,p1变为fulfilled状态,这样p1.then(f2)里面的函数 f2 才可以放进微任务队列里。 - 当f2里执行返回一个resolve / 全部执行完,才会把 p2 变成fulfilled状态。 总结完就是,上一链内部执行完,返回的fulfilled状态才能使下一链的函数放进任务队列。
注意
- 一定要画图,给匿名的箭头函数起名字f1 f2......,放入宏/微任务的队列。
- 分清函数的声明 / 调用
- 先看同步执行的代码,再看异步任务队列里的
复杂题
规则1:处于pending状态的Promise对象p,内部状态的resolve,会让p.then(fn)中的fn加入微任务队列
let p = new Promise(function f1(resolve) {
setTimeout(function f2(){
resolve(2)
console.log(1)
}, 1000)
})
p.then(function f3(v) { console.log(v) })
//1,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) })
//2,3,22,33
规则2:对于一处于fulfilled状态的Promise对象p,p.then(fn)会立即让fn加入微任务队列
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)
})
//2,22,3,33
规则1+规则2的应用:
let p1 = Promise.resolve()
.then(function f1(v) { console.log(1) })
.then(function f2(v) { console.log(2) })
.then(function f3(v) { console.log(3) })
p1.then(function f4(v) { console.log(4) })
p1.then(function f5(v) { console.log(5) })
let p2 = Promise.resolve()
.then(function f11(v) { console.log(11) })
.then(function f22(v) { console.log(22) })
.then(function f33(v) { console.log(33) })
p2.then(function f44(v) { console.log(44) })
p2.then(function f55(v) { console.log(55) })
// 1,11,2,22,3,33,4,5,44,55