美文网首页
由一道bilibili面试题看Promise异步执行机制

由一道bilibili面试题看Promise异步执行机制

作者: an_371e | 来源:发表于2021-04-29 07:56 被阅读0次
    var date = new Date() 
    
    console.log(1, new Date() - date) 
    
    setTimeout(() => {
        console.log(2, new Date() - date)
    }, 500) 
    
    Promise.resolve().then(console.log(3, new Date() - date)) 
    
    while(new Date() - date < 1000) {} 
    
    console.log(4, new Date() - date)
    

    求上面的输出顺序和输出值,为什么?

    答案:

    1 0
    3 1
    4 1000
    2 1000
    

    其中,关于时间差结果可能因为计算机性能造成的微小差异,可忽略不计

    你答对了吗?下面我们由浅入深探索本题

    由浅入深探索 Promise 异步执行

    首先,看一下 event loop 的基础必备内容

    event loop 执行顺序:

    • 首先执行 script 宏任务
    • 执行同步任务,遇见微任务进入微任务队列,遇见宏任务进入宏任务队列
    • 当前宏任务执行完出队,检查微任务列表,有则依次执行,直到全部执行完
    • 执行浏览器 UI 线程的渲染工作
    • 检查是否有Web Worker任务,有则执行
    • 执行下一个宏任务,回到第二步,依此循环,直到宏任务和微任务队列都为空

    微任务包括:MutationObserverPromise.then()或catch()Promise为基础开发的其它技术,比如fetch APIV8的垃圾回收过程、Node独有的process.nextTickObject.observe(已废弃;Proxy 对象替代)

    宏任务包括scriptsetTimeoutsetIntervalsetImmediateI/OUI renderingpostMessageMessageChannel

    注意: 下面的题目都是执行在浏览器环境下

    遇到不好理解的,可结合 promise 源码 进行理解,就很简单了

    1. 同步 + Promise

    题目一:

    var promise = new Promise((resolve, reject) => {
        console.log(1)
        resolve()
        console.log(2)
    })
    promise.then(()=>{
        console.log(3)
    })
    console.log(4)
    // 1
    // 2
    // 4
    // 3
    

    解析:

    • 首先明确, Promise 构造函数是同步执行的, then 方法是异步执行的
    • 开始 new Promise ,执行构造函数同步代码,输出 1
    • resolve(), 将 promise 的状态改为了 resolved ,并且将 resolve 值保存下来,此处没有传值
    • 执行构造函数同步代码,输出 2
    • 跳出promise,往下执行,碰到 promise.then 这个微任务,将其加入微任务队列
    • 执行同步代码,输出 4
    • 此时宏任务执行完毕,开始检查微任务队列,执行 promise.then 微任务,输出 3

    题目二:

    var promise = new Promise((resolve, reject) => {
        console.log(1)
    })
    promise.then(()=>{
        console.log(2)
    })
    console.log(3)
    // 1
    // 3
    

    解析:

    • 开始 new Promise ,执行构造函数同步代码,输出 1
    • promise.then ,因为 promise中并没有resolve ,所以 then 方法不会执行
    • 执行同步代码,输出 3

    题目三:

    var promise = new Promise((resolve, reject) => {
        console.log(1)
    })
    promise.then(console.log(2))
    console.log(3)
    // 1
    // 2
    // 3
    

    解析:

    • 首先明确, .then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传( value => value
    • 开始 new Promise ,执行构造函数同步代码,输出 1
    • 然后 then() 的参数是一个 console.log(2) (注意:并不是一个函数),是立即执行的,输出 2
    • 执行同步代码,输出 3

    题目四:

    Promise.resolve(1)
      .then(2)
      .then(Promise.resolve(3))
      .then(console.log)
    // 1
    

    解析:

    • then(2)then(Promise.resolve(3)) 发生了值穿透,直接执行最后一个 then ,输出 1

    题目五:

    var promise = new Promise((resolve, reject) => {
        console.log(1)
        resolve()
        reject()
    })
    promise.then(()=>{
        console.log(2)
    }).catch(()=>{
        console.log(3)
    })
    console.log(4)
    // 1
    // 4
    // 2
    

    解析:

    • 开始 new Promise ,执行构造函数同步代码,输出 1
    • resolve(), 将 promise 的状态改为了 resolved ,并且将 resolve 值保存下来,此处没有传值
    • reject() ,此时 promise 的状态已经改为了 resolved ,不能再重新翻转(状态转变只能是pending —> resolved 或者 pending —> rejected,状态转变不可逆)
    • 跳出promise,往下执行,碰到 promise.then 这个微任务,将其加入微任务队列
    • 往下执行,碰到 promise.catch 这个微任务,此时 promise 的状态为 resolved (非 rejected ),忽略 catch 方法
    • 执行同步代码,输出 4
    • 此时宏任务执行完毕,开始检查微任务队列,执行 promise.then 微任务,输出 2

    题目六:

    Promise.resolve(1)
      .then(res => {
        console.log(res);
        return 2;
      })
      .catch(err => {
        return 3;
      })
      .then(res => {
        console.log(res);
      });
    // 1
    // 2
    

    解析:

    • 首先 resolve(1), 状态改为了 resolved ,并且将 resolve 值保存下来
    • 执行 console.log(res) 输出 1
    • 返回 return 2 实际上是包装成了 resolve(2)
    • 状态为 resolvedcatch 方法被忽略
    • 最后 then ,输出 2

    2. 同步 + Promise + setTimeout

    题目一:

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

    解析:

    • 首先 setTimout 被放入宏任务队列
    • Promise.resolve().thenthen 方法被放入微任务队列
    • 执行同步代码,输出 3
    • 此时宏任务执行完毕,开始检查微任务队列,执行 then 微任务,输出 2
    • 微任务队列执行完毕,检查执行一个宏任务
    • 发现 setTimeout 宏任务,执行输出 1

    题目二:

    var promise = new Promise((resolve, reject) => {
      console.log(1)
      setTimeout(() => {
        console.log(2)
        resolve()
      }, 1000)
    })
    
    promise.then(() => {
      console.log(3)
    })
    promise.then(() => {
      console.log(4)
    })
    console.log(5)
    // 1
    // 5
    // 2
    // 3
    // 4
    

    解析:

    • 首先明确,当遇到 promise.then 时,如果当前的 Promise 还处于 pending 状态,我们并不能确定调用 resolved 还是 rejected ,只有等待 promise 的状态确定后,再做处理,所以我们需要把我们的两种情况的处理逻辑做成 callback 放入 promise 的回调数组内,当 promise 状态翻转为 resolved 时,才将之前的 promise.then 推入微任务队列
    • 开始, Promise 构造函数同步执行,输出 1 ,执行 setTimeout
    • setTimeout 加入到宏任务队列中
    • 然后,第一个 promise.then 放入 promise 的回调数组内
    • 第二个 promise.then 放入 promise 的回调数组内
    • 执行同步代码,输出 5
    • 检查微任务队列,为空
    • 检查宏任务队列,执行 setTimeout 宏任务,输入 2 ,执行 resolvepromise 状态翻转为 resolved ,将之前的 promise.then 推入微任务队列
    • setTimeout 宏任务出队,检查微任务队列
    • 执行第一个微任务,输出 3
    • 执行第二个微任务,输出 4

    回到开头

    现在看,本题就很简单了

    var date = new Date() 
    
    console.log(1, new Date() - date) 
    
    setTimeout(() => {
        console.log(2, new Date() - date)
    }, 500) 
    
    Promise.resolve().then(console.log(3, new Date() - date)) 
    
    while(new Date() - date < 1000) {} 
    
    console.log(4, new Date() - date)
    

    解析:

    • 首先执行同步代码,输出 1 0
    • 遇到 setTimeout ,定时 500ms 后执行,此时,将 setTimeout 交给异步线程,主线程继续执行下一步,异步线程执行 setTimeout
    • 主线程执行 Promise.resolve().then , .then 的参数不是函数,发生值透传( value => value ) ,输出 3 1
    • 主线程继续执行同步任务 whlie ,等待 1000ms ,在此期间,setTimeout 定时 500ms 完成,异步线程将 setTimeout 回调事件放入宏任务队列中
    • 继续执行下一步,输出 4 1000
    • 检查微任务队列,为空
    • 检查宏任务队列,执行 setTimeout 宏任务,输入 2 1000

    总结

    • Promise 构造函数是同步执行的, then 方法是异步执行的

    • .then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传

    • Promise的状态一经改变就不能再改变,构造函数中的 resolvereject 只有第一次执行有效,多次调用没有任何作用

    • .then方法是能接收两个参数的,第一个是处理成功的函数,第二个是处理失败的函数,再某些时候你可以认为catch.then第二个参数的简便写法

    • 当遇到 promise.then 时, 如果当前的 Promise 还处于 pending 状态,我们并不能确定调用 resolved 还是 rejected ,只有等待 promise 的状态确定后,再做处理,所以我们需要把我们的两种情况的处理逻辑做成 callback 放入 promise 的回调数组内,当 promise 状态翻转为 resolved 时,才将之前的 promise.then 推入微任务队列

    每天三分钟,进阶一个前端小 tip
    面试题库
    算法题库

    相关文章

      网友评论

          本文标题:由一道bilibili面试题看Promise异步执行机制

          本文链接:https://www.haomeiwen.com/subject/mqeirltx.html