乐趣区

关于前端:Promise宏任务微任务相关易错点汇总

Promise相干面试题除去手写题(相干内容参考手写Promise),最常考的就是输入后果程序。

该局部为 Promise 输入后果相干易错点的了解和解析。

async/await 相干输入

async function f1() {console.log(1)
    await f2()
  console.log(3)
}
async function f2() {console.log(2)
}
Promise.resolve().then(()=>{console.log(4)
})
f1()
console.log('start')

// 最初输入为:1 2 start 4 3

如果没有 async/await,只有 Promise,这里了解起来会容易很多。因而这里易错点分两局部:

  • 如何将 async/await 本义为 Promise 表达式
  • new Promise(fn)fn 函数何时执行

async/await 的本义

愿函数

async function f1() {console.log(1)
    await f2()
  console.log(3)
}
async function f2() {console.log(2)
}

本义后

function f1() {return new Promise((resolve)=>{console.log(1)
    const p = f2()
    p.then(()=>{console.log(3)
      resolve()})
  })
}
function f2() {return new Promise((resolve)=>{console.log(2)
    resolve()})
}
f1() // 1 2 3

本义后的代码,执行 f1 时,顺着 Promise 的链路,很容易判断输入后果是1 2 3

new Promise(fn)fn 函数何时执行

之前我的项目中有依照 Promise A+ 协定实现自定义的 Promise(参考地址),次要看这么一段:

class MyPromise {
  status = statusEnum.PENDING;
  resArr = [];
  rejArr = [];
  value = null;
  reason = null;
  constructor(fn) {const resolve = (val) => {// ...};
    const reject = (val) => {// ...};
    try {fn(resolve, reject);
    } catch (err) {reject(err);
    }
  }
  then(onResolve, onReject) {// ...}
  catch(onReject) {// ...}
}

能够看到,new Promise(fn)时,fnconstructor 中立刻执行,即能够将其看作同步代码。再看之前的示例,联合本义后的后果,输入更容易了解

async function f1() {console.log(1) // 在 f1 返回的 Promise 对象的 construstor 中立刻执行
    await f2()
  console.log(3)  // 塞入微工作
}
async function f2() {console.log(2) // 在 f2 返回的 Promise 对象的 construstor 中立刻执行
}
Promise.resolve().then(()=>{  // 塞入微工作
  console.log(4)
})
f1() // 先输入立刻执行的后果 1 2
console.log('start') // 输入立刻执行的后果 start
// 执行微工作阶段,此时微工作队列为[()=>{console.log(4)}, ()=>{console.log(3)}]
// 执行微工作队列,输入 4 3

宏工作和 js 线程工夫片

Promise.resolve().then(()=>{setTimeout(()=>{console.log(2)
    })
})
setTimeout(()=>{Promise.resolve().then(()=>{console.log(1)
    })
})
// 输入 1 2

剖析以上代码,先看第一个宏工作阶段执行的内容

// 假如存在 pushToNextMacroTask 办法,将 function 推入下一个宏工作执行

// 第一个宏工作阶段
const microTask = []
microTask.push(()=>{setTimeout(()=>{console.log(2)
  })
})
pushToNextMacroTask(()=>{Promise.resolve().then(()=>{console.log(1)
    })
}, 0)
// 执行第一个宏工作的微工作阶段
microTask.forEach(func => func())
/**
 * 此时执行了如下内容
 * pushToNextMacroTask(()=>{console.log(2)
  }, 0)
 */

这里开始存在一个 分歧点 ,第一个宏工作阶段,别离执行了两次pushToNextMacroTask,将办法推至下一个宏工作,那么 下一个宏工作 是否是同一个宏工作?对该局部代码打 Performance 快照后,后果如下:

能够发现,尽管两个 setTimeout 定时的工夫都是 0,但并不会将工作放到同一个宏工作中。最终后果解析如下:

Promise.resolve().then(()=>{setTimeout(()=>{console.log(2)
    })
})
setTimeout(()=>{Promise.resolve().then(()=>{console.log(1)
    })
})

/**
 * 假如宏工作列表:macroTaskList = []
 * 假如微工作列表:microTaskList = []
 * 1. 第一个宏工作阶段
 * 执行 Promise.resolve().then, 将其回调塞入微工作队列
 * microTaskList = [*  ()=>{*   setTimeout(()=>{*      console.log(2)
 *   })
 *  }
 * ]
 * 执行 setTimeout, 将其回调函数放入下一个宏工作
 * macroTaskList = [*  ()=>{console.log(1)}
 * ]
 * 1.1 第一个宏工作的微工作阶段
 * const microTask = macroTaskList.shift()
 * microTask()
 * 执行 setTimeout,将其回调塞入下一个宏工作
 * macroTaskList = [*  ()=>{console.log(1)},
 *  ()=>{console.log(2)}
 * ]
 * 2. 第二个宏工作阶段
 * const macroTask = macroTaskList/shift()
 * macroTask() // 输入 1
 * 2.2 第二个宏工作的微工作阶段,没有微工作
 * 3. 第三个宏工作阶段
 * const macroTask = macroTaskList/shift()
 * macroTask() // 输入 2
 * 
 * 总结:* 输入 1 2
 */

js线程和渲染线程互斥,浏览器调配给 js 线程执行的一个工夫片中,蕴含多个宏工作 。先后执行两次setTimeout(fn, 0),两个fn 会别离放入两个宏工作中,而非同一个宏工作。

退出移动版