大廠經常會圍繞Promise 來展開面試,我們就從面試角度反推,學好重要的知識點吧~
一、結合真題來搞懂H,P,T
概念: H是宏任務,P是promise為代表的微任務,T是setTimeout這類時間函數
什么宏?,什么微? , 什么任務??? ====> 宏任務與微任務必知
-
看屁看,這么多,就兩句話:::
- 宏任務 先(同一輪中)
- script 2. setTimeout/setInterval 3. UI rendering/UI事件 4. postMessage,MessageChannel 5. setImmediate,I/O(Node.js)
- 微任務 后(同一輪中)
- 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之前。
行,答案呼之欲出,歡迎打在評論區交流下,撰文不易,多多點贊支持下喲~( ̄▽ ̄)"