大廠面試Promise必備(下)

大廠經常會圍繞Promise 來展開面試,我們就從面試角度反推,學好重要的知識點吧~

一、結合真題來搞懂H,P,T

  • 概念: H是宏任務,P是promise為代表的微任務,T是setTimeout這類時間函數

  • 什么宏?,什么微? , 什么任務??? ====> 宏任務與微任務必知

  • 看屁看,這么多,就兩句話:::

    • 宏任務 先(同一輪中)
        1. script 2. setTimeout/setInterval 3. UI rendering/UI事件 4. postMessage,MessageChannel 5. setImmediate,I/O(Node.js)
    • 微任務 后(同一輪中)
        1. Promise 2.process.nextTick(Node.js) 3. Object.observe(已廢棄;Proxy 對象替代)4. MutaionObserver
  • 理論1: H=>P=>T

  • 理論2: H=>P=>H>P.... (T屬于H)

Tips: 話不多說,看題~

1.P

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

題解: 1,2
解析: Promise.resolve直接給數字1,那返回res 即1,到第1個console,輸出1; 緊接著return 數字2,無異常,不catch走then,返回res為上面return的輸出2

2.H + P

const 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中,按照上下文,走1,遇resolve,自行,把promise 的狀態由pending改為了fulFilled,但這沒有影響,繼續走2,后執行promise,then是異步函數,丟微事件隊列,走4,第一輪宏任務結束,走微任務,在積攢的事件隊列中,按照先進先出,就一個then,走3。

3.H + P + T

    const promise1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('success')
      }, 1000)
    })
    const promise2 = promise1.then(() => {
      console.log(1)
      // throw new Error('error!!!')
    })

    console.log('promise1', promise1)
    console.log('promise2', promise2)

    setTimeout(() => {
      console.log('promise3', promise1)
      console.log('promise4', promise2)
    }, 2000)

題解: promise1, promise2, 1, promise3, promise4
解析: 定義promise1,沒有輸出,走定義promise2,執行了promise1,發現一個T宏任務,丟下一個宏事件隊列,緊接著遇then 是個異步函數,丟微事件隊列,這次的宏任務查詢還沒完,往下又一個T宏任務,丟宏事件隊列,再執行微任務,輸出1,再執行第二輪宏任務,先入先出,resolve('success') 改掉promise1的status,但這沒有影響,再走下一個宏任務,輸出promise3,和promise4。

Tips: 這道題中前2個promise1,promise2,定義未執行,打印默認狀態是pending,promise3打印時,promise1已經resolve了success,是fulFilled,值為'success',promise4 也是fulFilled,只是promise2沒有返回值,返回值默認是undefined

  • 發現了嗎,上述還有一個注釋,我們放開這行報錯會有什么不同??

解析: 順序還是那個順序,只是輸出1變成拋出一個錯,promise4的執行結果中的promise2是rejected,返回值不再是undefined,而是Error

4.H + P + T

new Promise(resolve => {
    console.log(1);
    setTimeout(() => console.log(2),0)
    Promise.resolve().then(() => console.log(3))
    resolve();
}).then(() => console.log(4))
console.log(5)

題解: 1, 5, 3, 4, 2
解析: 很典型,定義Promise時,輸出1,遇到T宏任務,丟下一輪宏事件隊列,遇promise立即resolve,修改了status,但這沒有影響,then是異步函數,本輪丟微事件隊列,緊接著resolve當前的promise,修改了status,但這沒有影響,再遇到then是異步函數,丟本輪微事件隊列,繼續往下找,輸出5,然后開始執行本輪的微事件隊列,先入先出,輸出3,4,再處理下一輪宏事件隊列中,也是先入先出,執行2。

Tips: 這里的setTimeout 時間雖然是0,但不影響丟事件隊列以及后續的執行順序,這題也充分應證了我們上述的理論1 H=>P => T 由于T 函數也是宏任務,所以也符合理論2 H=>P => H=>P

5.P

const promise = new Promise((resolve, reject) => {
  resolve('success1')
  reject('error')
  resolve('success2')
})
promise
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })

題解: then: success1
解析: 故意放一道簡單點的題,幫大家樹立起信心去大廠廝殺~ 這題考的是狀態只能從p=>f 或p=>r, 是不可逆的,這走了resolve,后到then,就不會走catch了

6.H + P + T

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('once')
  }, 1000)
 resolve('success')
})
promise.then((res) => {
  console.log(res + 1)
})
promise.then((res) => {
  console.log(res + 2)
})

題解: success1,success2,once
解析: 定義promise,遇到T宏任務,丟宏事件隊列,定義start,遇then異步函數,丟微事件隊列,遇then異步函數,丟微事件隊列,然后開始執行微事件隊列,先入先出,輸出'success1' 然后是再輸出一遍,輸出'success2' ,然后執行下一輪宏任務,輸出once

  • 那我們變一變,把promise的resolve 放進setTimeout 函數里,會怎么樣??
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('once')
    resolve('success')
  }, 1000)
})
promise.then((res) => {
  console.log(res + 1)
})
promise.then((res) => {
  console.log(res + 2)
})

題解: once, success1, success2
解析: 奇怪,怎么這次和我們前面的理論不匹配了呢??,差異是resolve放到了T宏任務中,我們再仔細考慮下,定義promise,卻不給resolve和reject的時候,then是不會執行的,也就是只有執行到了then異步函數才會丟到微事件隊列去,所以這里反而是要等setTimeout函數執行完,才去執行這兩個then,當然也是先入先出執行。再看上面的題,都是在promise定義中就resolve或者reject了~

那我們趁熱打鐵~

7.H + P + T

new Promise(resolve => {
    console.log(1);
    setTimeout(() => {resolve();console.log(2)},0)
    Promise.resolve().then(() => console.log(3))
}).then(() => console.log(4))
console.log(5)

題解: 1, 5, 3, 2, 4
解析: 定義promise,輸出1,遇到T宏任務,丟宏事件隊列,緊接著的promise直接resolve,因此遇到并執行then是異步函數,丟進微事件隊列,所定義的Promise 沒有resolve或reject,因此不會執行到它的then,那就繼續往下,執行輸出5,后執行微事件隊列,輸出3,然后執行下一輪宏事件隊列,遇到resolve,這時候,會調用剛開始定義的Promise的then是個異步函數,丟這輪的微事件隊列,執行輸出2,本輪宏任務結束,執行微事件隊列,輸出4。

Tips: Promise.resolve().then() 這行為什么沒有把new 的Promise 給resolve掉呢,我們要理解new Promise 是通過new 構造函數Promise得到一個Promise實例,而Promise.resolve() 會生成一個thenable的對象,也就是一個新的Promise實例,因此并不是同一個~

8.P

Promise.resolve()
      .then(() => {
        return new Error('error1')
        // throw Error('error1')
      })
      .then(res => {
        console.log('then: ', res)
      })
      .catch(err => {
        console.log('catch1: ', err)
        throw Error('error2')
      })
      .catch(err => {
        console.log('catch2: ', err)
      })

題解: then: Error:error1
解析: 這題主要考細節,這里return 的是一個Error 對象,所以是走then,而不是catch,throw一個Error 才是走catch。

  • 那假設我們把注釋放開,拋個錯,是什么結果呢,答案是catch1: Error1, catch2:Error2 要注意的是catch 可以捕獲緊挨著上個catch 中的異常 還記得上一篇我們說到catch 只是then的語法糖,本身就是thenable的,所以可以一直catch下去。

9.P

Promise.resolve()
      .then(
        function success1(res) {
          throw new Error('error')
        },
        function fail1(e) {
          console.error('fail1: ', e)
        }
      )
      .then(
        function success2(res) {},
        function fail2(e) {
          console.error('fail2: ', e)
        }
      )

題解: fail2: Error:error
解析: 這題就更需要理解catch 是then的語法糖了,以及then的兩個參數(resolve, reject) ,Promise.resolve順利執行,走resolve函數,這里就是success1,拋了個異常,那就看下面有沒有catch,一看沒有,但是有個then帶著兩個參數,我們經常看到then只有一個參數,是把第二個reject 省略了,默認只會執行resolve函數,而catch(reject) === then(undefined, reject),因此這里執行fail2,輸出fail2: Error:error。

10.H + P + T

   Promise.resolve().then(() => {
      console.log('then')
    })
   process.nextTick(() => {
      console.log('nextTick')
    })
    setImmediate(() => {
      console.log('setImmediate')
    })
    console.log('end')

題解: end, nextTick,then, setImmediate
解析: 這題主要考的是常見的HPT有哪些,H 普通宏任務,P是異步微任務,T 也是宏任務。其實也很好記,微任務我們能見到的就是promise 和process.nextTick, 其它都是宏任務,而script 就是我們腳本,也就是第一個宏任務。那再看這題,上來其實是第一個宏任務,腳本script自上而下執行,遇到nextTick,丟微事件隊列,緊接著promise直接resolve,執行then為異步函數,丟微事件隊列,然后是一個宏任務,丟下一輪宏事件隊列,往下輸出end,然后執行微事件隊列,先入先出,但是,這里要注意process.nextTick() 優先級是高于Promise的,輸出nextTick,再輸出then,然后執行第二輪宏任務,輸出setImmediate

Tips 很多同學拷貝代碼進vue項目,vue3報了個process undefined,因為移除了, 有的還報setImmediate不兼容谷歌。vue2的話發現順序好像并不是這樣,這里必須強調是node環境下,跑一個js,所以請大家新建一個js,并在命令行node 這個js去實踐~

但是既然涉及到了vue,我們來看下面這個有意思的事情

11.H + P + T 在vue環境下!!

   // 大家都知道vue 中有個this.$nextTick,很少會去用process.nextTick
   // !! 所以我們這里就談this.$nextTick
    this.$nextTick(() => {
        console.log("nextTick1")
    })
    Promise.resolve().then(() => {
        console.log("then1")
    })
    setTimeout(() => {
         console.log("setTimeout")
    }, 0)
    this.$nextTick(() => {
         console.log("nextTick2")
    })
    Promise.resolve().then(() => {
    console.log("then2")
  })

題解: nextTick1,nextTick2,then1,then2,setTimeout
解析: 首先我們要知道vue的nextTick本質上是對Promise 的封裝,那本來應該就看成promise 就可以啦,按照微任務的事件隊列應該從上到下哇??但關鍵是: vue內部的promise實例一直是同一個,也就是你寫后面的nextTick 會被合并到一個promise中去執行的,先入先出,nextTick1, nextTick2,然后執行異步事件隊列,then1,2,最后第二輪宏事件隊列輸出setTimeout

12.H + P + T

  const first = () => (new Promise((resolve, reject) => {
    console.log(3);
    let p = new Promise((resolve, reject) => {
        console.log(7);
        setTimeout(() => {
            console.log(5);
            resolve(6);
        }, 0)
        resolve(1);
    });
    resolve(2);
    p.then((arg) => {
        console.log(arg);
    });
}));
first().then((arg) => {
    console.log(arg);
});
console.log(4);

題解: 3,7,4,1,2,5
解析: 這個題涉及的東西很多,但是按照我們的理論2走,是很簡單的,我們來理一下,首先,定義first,會執行輸出3,定義了一個p 是promise 對象,定義中有輸出7,遇到T函數,丟宏事件隊列,緊接著resolve,那就要找到then去執行,看到下面有p的then是異步函數,承接的是數值1,丟微事件隊列,繼續走first 被resolve,找到first的then是異步函數,承接的數值是2,丟微事件隊列,繼續走,發現輸出4,然后執行微事件隊列,先入先出,輸出1,2,再執行下一輪宏事件隊列,輸出5,緊接著resolve(6),有同學蒙了,輸出6 一個Promise 的狀態修改是不可逆的,也就是不可以resolve reject 多次,因此不會輸出6。

13.P

  var p = new Promise((resolve, reject) => {
      return Promise.reject(Error('error'))
    })
    p.catch(error => console.log(error.message))
    p.catch(error => console.log(error.message))

題解: Error: error
解析: 沒錯,前面的題目可能稍微有點復雜,這里再給你樹立起信心!!這個題目主要考的是如果Promise return 的是一個Promise,那么then的結果就會是這個Promise,我們知道就算return的是個數值,給到后面的也會包裝成一個thenable的promise,因此這里沒有拋出異常,只是把這個promise傳下去了,那catch 也沒東西可catch~

14.H + P + T

   var p = new Promise((resolve, reject) => {
      return Promise.reject(Error('error'))
    })
    p.catch(error => console.log(error.message))
    p.then(res => console.log(res))

題解: Error: error
解析: 沒錯,前面的題目可能稍微有點復雜,這里再給你樹立起信心!!這題return 出去,但是既沒有resolve,也沒有reject,下面都不會觸發,但是return的結果是個可執行的函數,拋出了一個異常Error: error

15.H + P + T + async

   async function async1() {
      console.log(1)
      const result = await async2()
      console.log(3)
    }
    async function async2() {
      console.log(2)
    }
    Promise.resolve().then(() => {
      console.log(4)
    })
    setTimeout(() => {
      console.log(5)
    })
    async1()
    console.log(6)

題解: 1,2,6,4,3,5
解析: 這題主要在于async,await,async函數是generate的語法糖,在這里async1中的await async2() 我們要怎么去看待呢,首先, 定義async 1 是個function,沒有自行,async2也是,到了promise,直接resolve,then異步函數丟微事件隊列,遇T宏任務丟宏事件隊列,往下執行async1,輸出1,遇到await async2,這部分相當于Promise((resolve,reject)=>{console.log(2)}).then(res=>{console.log(3)}),因此輸出2直接執行,輸出3丟微事件隊列,追究下result 是什么?? 由于沒有return值,result 就是個undefined,注意這時候第一輪宏任務還沒完,還有個輸出6,然后執行微事件隊列,先入先出原則,是4,3,最后執行第二輪宏任務中的輸出5。

16.H + P + T + async

   async function async1() {
      console.log('1')
      await async2()
      console.log(2)
      Promise.resolve().then(() => {
        console.log(9)
      })
    }
    async function async2() {
      console.log('3')
    }
    console.log('4')
    setTimeout(() => {
      console.log('5')
    }, 0)
    async1()
    new Promise(function (reslove) {
      console.log('6')
      reslove()
    }).then(function () {
      console.log('7')
    })
    console.log('8')

貓咪

喝杯茶歇會

題解: 你在想Peach? 來試試手吧,這題和上面的很類似,不過要小心的是async2里的then函數執行的不是簡單的輸出,而是又一個異步函數,我們之前只是看作Promise((resolve,reject)=>{console.log(2)}).then(res=>{console.log(3)}),現在 console.log(2)
Promise.resolve().then(() => {
console.log(9)
})
整體替換了之前的3,所以執行這個整體時,先會執行2,沒錯,但是緊接著遇到直接resolve的promise,then是異步函數,把輸出9丟這一輪的微事件隊列中去,7 和 3 是同級別的,明白了吧,而這個9 是執行3時丟微事件隊列,它要比3,7都晚,但是好歹是微事件隊列,因此還是在5之前。

行,答案呼之欲出,歡迎打在評論區交流下,撰文不易,多多點贊支持下喲~( ̄▽ ̄)"

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容