這兩天一直在看有關(guān)Promise的內(nèi)容吻商,但是不太確定怎樣才叫做真正掌握,所以先把目前自己的理解寫下來糟红,總結(jié)一下艾帐。
文章最后的一小節(jié)【練習(xí)】我也加入了 很 多 遇到的有趣的promise面試題,大家順便可以檢測一下自己的知識是否鞏固盆偿。
章節(jié)直通車:
概念
Promise是跟隨es6出來對異步回調(diào)地獄的一種很好的解決方案柒爸,之前的很多異步回調(diào)是很難檢測他報錯的,所以就用Promise(承諾)這個單詞來向廣大程序員保證:“我可以做到”事扭,緊接著風(fēng)靡前端捎稚。(以上都是鬼扯)
Promise是異步的一種解決方案,他有三種狀態(tài):pending(等待)句旱、fulfilled(完成)阳藻、rejected(失敗)谈撒。
用法
基礎(chǔ)用法
首先根據(jù)下面的代碼自己腦補(bǔ)一下會打印什么:
new Promise(function(resolve, reject) {
console.log(1);
resolve('success');
console.log(2);
reject('error');
console.log(3);
}).then(function(value) {
console.log('then', value);
}).catch(function(err) {
console.err('error:', err);
});
|
|
|
|
|
正確答案是
1
2
3
then success
我們根據(jù)上面的情況來具體聊聊Promise腥泥,
- Promise必須存在一種狀態(tài)(pending或fulfilled或rejected)。如果為pending狀態(tài)啃匿,那么可以轉(zhuǎn)換到其他的兩個狀態(tài)蛔外;如果為fulfilled狀態(tài)蛆楞,那么必須有一個值,并且值和狀態(tài)不可改變夹厌;如果為rejected狀態(tài)豹爹,那么必須有一個原因,并且狀態(tài)和原因不可改變矛纹。
上面的說法是人為約定俗成的(可參考Promise/A+規(guī)范)臂聋,所以我們當(dāng)我們看到 resolve('success');
后面還有reject('error');
,因?yàn)榍罢郀顟B(tài)已經(jīng)改變?yōu)閒ulfilled或南,所以狀態(tài)不會改變孩等,往后傳入值success
。
then方法接受兩個參數(shù)采够,兩個參數(shù)都需要為函數(shù)肄方,否則會出現(xiàn)值穿透現(xiàn)象;then方法會繼續(xù)返回一個新的Promise蹬癌;then需要return一個值权她,如果沒有return值,默認(rèn)return的為undefined逝薪。
由于catch和then返回的都是一個新的Promise隅要,所以他可以繼續(xù)往后鏈?zhǔn)秸{(diào)用。
then(function(value) {
console.log('then', value);
})
這里值傳了一個函數(shù)董济,獲取到的value是resolve傳的字符串success拾徙。
- catch是語法糖,他其實(shí)相當(dāng)于then(null, function failed() {})感局。
We have a problem with promises
之所以這個小節(jié)是英文名尼啡,是因?yàn)橹霸赥witter上有一個很火的文章 —— 《We have a problem with promises》 原文翻譯,推薦大家深入看看询微,這里我就把里面的四道題拿出來崖瞭,我們繼續(xù)探討一下。
判斷下面四段代碼的區(qū)別:
聲明:在這些例子中撑毛,假定 doSomething() 和 doSomethingElse() 均返回 promises书聚,并且這些 promises 代表某些在 JavaScript event loop (如 IndexedDB, network, setTimeout) 之外的某些工作結(jié)束,這也是為何它們在某些時候表現(xiàn)起來像是并行執(zhí)行的意義藻雌。
假設(shè)doSomething并沒有返回值雌续。
- 代碼1
doSomething().then(function () {
return doSomethingElse();
}).then(finalHandler);
這里的代碼中規(guī)中矩,return了一個新的promise胯杭,所以后面的then都是基于doSomethingElse返回的promise往后繼續(xù)執(zhí)行的驯杜。
doSomething
|-----------------|
doSomethingElse(undefined)
|------------------|
finalHandler(resultOfDoSomethingElse)
|------------------|
- 代碼2
doSomething().then(function () {
doSomethingElse();
}).then(finalHandler);
doSomething
|-----------------|
doSomethingElse(undefined)
|--------------------------------|
finalHandler(undefined)
|------------------|
第一個then沒有返回值,所以默認(rèn)是返回undefined做个;
其次doSomethingElse()是一個異步操作鸽心,所以finalHandler會在doSomething執(zhí)行完之后(中間執(zhí)行的一些時間可以直接忽略掉)開始滚局。
- 代碼3
doSomething().then(doSomethingElse())
.then(finalHandler);
doSomething
|-----------------|
doSomethingElse(resultOfDoSomething)
|--------------------------------|
finalHandler(undefined)
|------------------|
因?yàn)閠hen傳遞的參數(shù)是一個自執(zhí)行的函數(shù),所以doSomething和doSomethingElse相當(dāng)于同步執(zhí)行顽频,then默認(rèn)傳遞undefined藤肢,所以等doSomething執(zhí)行完后,finalHandler會繼續(xù)執(zhí)行糯景。
- 代碼4
doSomething().then(doSomethingElse)
.then(finalHandler);
因?yàn)閐oSomethingElse本身是一個function嘁圈,then本身是接受function的,所以這個和代碼1(只是在外面又包了一層function)是相同的蟀淮。
doSomething
|-----------------|
doSomethingElse(undefined)
|------------------|
finalHandler(resultOfDoSomethingElse)
|------------------|
練習(xí)
練習(xí)1:
let a = Promise.reject('a').then(() => {
console.log('a passed')
}).catch(() => {
console.log('a failed')
});
Promise.reject('b').catch(() => {
console.log('b failed');
}).then(() => {
console.log('b passed');
});
|
|
|
|
|
正確答案:
b failed
a failed
b passed
原因:
如果認(rèn)為描述的還不夠清楚丑孩,可以參考這篇文章 講解
- catch是其實(shí)就是then(null, function(){}),所以他也是一步一步串聯(lián)著往后運(yùn)行灭贷,并不會特殊的跳到后面;
- 涉及到了JavaScript的執(zhí)行順序問題:
(1)JavaScript分為宏任務(wù)和微任務(wù)(兩者一般都為異步任務(wù))略贮;特殊的甚疟,主線程也屬于宏任務(wù)。
微任務(wù)有 Promise, I/O 等等
宏任務(wù)有 setTimeout, setInterval 等等
其中逃延,宏任務(wù)和微任務(wù)是交替進(jìn)行的览妖,先是第一波宏任務(wù)和第一波微任務(wù),然后再是第二波宏任務(wù)和微任務(wù) ··· ···(按順序then往下走揽祥,就可以分為第N波任務(wù))
let a = Promise.reject('a')
相當(dāng)于
let a = new Promise((resolve, reject) => {
reject('a');
})
所以這個屬于主線程讽膏,立即執(zhí)行。
所以代碼中宏任務(wù)和微任務(wù)可以按照下面分類和執(zhí)行
先拆分
let p1 = Promise.reject('a').then(() => {
console.log('a passed')
});
p1.catch(() => {
console.log('a failed')
});
let p2 = Promise.reject('b').catch(() => {
console.log('b failed');
});
p2.then(() => {
console.log('b passed');
});
然后排序
// 一級微任務(wù)
let p1 = Promise.reject('a').then(() => {
console.log('a passed')
});
let p2 = Promise.reject('b').catch(() => {
console.log('b failed');
});
// 二級微任務(wù)
p1.catch(() => {
console.log('a failed')
});
p2.then(() => {
console.log('b passed');
});
所以按照順序打印拄丰,由于p1拋出異常府树,緊跟著的是then不運(yùn)行,p2拋出異常料按,緊跟著的是catch奄侠,打印b failed;由于沒有宏任務(wù)所以直接運(yùn)行二級微任務(wù)载矿,p1找到了catch所以輸出a failed垄潮;p2由于catch正常運(yùn)行,返回了一個新的promise并且沒有拋出異常闷盔,所以接著往下走弯洗,打印b passed。
練習(xí)2:
setTimeout(function () {
console.log(1);
}, 0)
new Promise(function (resolve) {
console.log(2);
for (var i = 0; i < 100; i++) {
i == 99 && resolve();
}
console.log(3);
}).then(function () {
console.log(4);
})
console.log(5);
|
|
|
|
|
正確答案:
2
3
5
4
1
原因:
其實(shí)我們在上一題解釋過宏任務(wù)和微任務(wù)逢勾,這里我就直接拆分并且排序一下
// 主線程
const p1 = new Promise(function (resolve) {
console.log(2);
for (var i = 0; i < 100; i++) {
i == 99 && resolve();
}
console.log(3);
});
console.log(5);
// 一級宏任務(wù)
setTimeout(function () {
console.log(1);
}, 0)
// 一級微任務(wù)
p1.then(function () {
console.log(4);
})
主線程 new Promise直接運(yùn)行牡整,打印2,3隨后console打印5,由于主線程屬于宏任務(wù)溺拱,所以接下來運(yùn)行一級微任務(wù)打印4果正,最后運(yùn)行一級宏任務(wù)炎码,打印1。
練習(xí)3:
Promise.resolve(1)
.then((res)=>{
console.log(res)
return 2
})
.catch( (err) => 3)
.then(res=>console.log(res))
|
|
|
|
|
正確答案:
1
2
原因:
打印1的原因很簡單秋泳,認(rèn)為resolve了一個值為1潦闲,被下面的then接收到了,其次由于then返回的promise沒有拋出錯誤迫皱,所以緊接著下面的catch沒有運(yùn)行歉闰,繼續(xù)往下鏈?zhǔn)秸{(diào)用,打印了上一個then返回的值2卓起。
練習(xí)4:
Promise.resolve(1)
.then( (x) => x + 1 )
.then( (x) => {throw new Error('My Error')})
.catch( () => 1)
.then( (x) => x + 1)
.then((x) => console.log(x))
.catch( (x) => console.log(error))
|
|
|
|
|
正確答案:
2
原因:
我們需要明確的是catch捕獲錯誤了之后還是可以繼續(xù)進(jìn)行鏈?zhǔn)秸{(diào)用的和敬。
這個題目的迷惑就是為什么then拋出了錯誤之后,沒有被捕獲到戏阅;原因是catch本身是可以捕獲到的昼弟,可是在后面的catch沒有處理錯誤,而是直接返回了1奕筐,所以后面就順風(fēng)順?biāo)淖吡藘蓚€then舱痘,打印了了一個2。
后面四個問題离赫,如果你還是沒有明白芭逝,可以參考文章: 后四個題解析
好啦,文章結(jié)束~歡迎大家指出錯誤渊胸!