Promise 与 宏/微任务

523 阅读4分钟

宏任务 与 微任务

浏览器中并不存在宏任务,宏任务(Macrotask)是 Node.js 发明的术语(Node.js里有六个阶段,每个阶段可以创建宏任务和微任务)。浏览器中只有任务(Task)和微任务(Microtask),一般入口都是任务,在任务的间隙插入微任务。

  1. 使用 script 标签、setTimeout 可以创建任务
  2. 使用 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加入微任务队列。

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

参考阅读