Promise詳解(含面試題精華講解)!组砚!

這兩天一直在看有關(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腥泥,

  1. 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

  1. then方法接受兩個參數(shù)采够,兩個參數(shù)都需要為函數(shù)肄方,否則會出現(xiàn)值穿透現(xiàn)象;then方法會繼續(xù)返回一個新的Promise蹬癌;then需要return一個值权她,如果沒有return值,默認(rèn)return的為undefined逝薪。

  2. 由于catch和then返回的都是一個新的Promise隅要,所以他可以繼續(xù)往后鏈?zhǔn)秸{(diào)用。

then(function(value) {
  console.log('then', value);
})

這里值傳了一個函數(shù)董济,獲取到的value是resolve傳的字符串success拾徙。

  1. 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. 代碼1
doSomething().then(function () {
  return doSomethingElse();
}).then(finalHandler);

這里的代碼中規(guī)中矩,return了一個新的promise胯杭,所以后面的then都是基于doSomethingElse返回的promise往后繼續(xù)執(zhí)行的驯杜。

doSomething
|-----------------|
                  doSomethingElse(undefined)
                  |------------------|
                                     finalHandler(resultOfDoSomethingElse)
                                     |------------------|
  1. 代碼2
doSomething().then(function () {
  doSomethingElse();
}).then(finalHandler);
doSomething
|-----------------|
                  doSomethingElse(undefined)
                  |--------------------------------|
                  finalHandler(undefined)
                  |------------------|

第一個then沒有返回值,所以默認(rèn)是返回undefined做个;

其次doSomethingElse()是一個異步操作鸽心,所以finalHandler會在doSomething執(zhí)行完之后(中間執(zhí)行的一些時間可以直接忽略掉)開始滚局。

  1. 代碼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í)行糯景。

  1. 代碼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)為描述的還不夠清楚丑孩,可以參考這篇文章 講解

  1. catch是其實(shí)就是then(null, function(){}),所以他也是一步一步串聯(lián)著往后運(yùn)行灭贷,并不會特殊的跳到后面;
  2. 涉及到了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é)束~歡迎大家指出錯誤渊胸!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末旬盯,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子翎猛,更是在濱河造成了極大的恐慌胖翰,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件切厘,死亡現(xiàn)場離奇詭異泡态,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)迂卢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門某弦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人而克,你說我怎么就攤上這事靶壮。” “怎么了员萍?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵腾降,是天一觀的道長。 經(jīng)常有香客問我碎绎,道長螃壤,這世上最難降的妖魔是什么抗果? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮奸晴,結(jié)果婚禮上冤馏,老公的妹妹穿的比我還像新娘。我一直安慰自己寄啼,他們只是感情好逮光,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著墩划,像睡著了一般涕刚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上乙帮,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天杜漠,我揣著相機(jī)與錄音,去河邊找鬼察净。 笑死驾茴,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的塞绿。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼恤批,長吁一口氣:“原來是場噩夢啊……” “哼异吻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起喜庞,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤诀浪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后延都,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體雷猪,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年晰房,在試婚紗的時候發(fā)現(xiàn)自己被綠了群嗤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嫩痰。...
    茶點(diǎn)故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出晰筛,到底是詐尸還是另有隱情,我是刑警寧澤囊扳,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布瓤荔,位于F島的核電站,受9級特大地震影響海蔽,放射性物質(zhì)發(fā)生泄漏共屈。R本人自食惡果不足惜绑谣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拗引。 院中可真熱鬧借宵,春花似錦、人聲如沸寺擂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽怔软。三九已至垦细,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間挡逼,已是汗流浹背括改。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留家坎,地道東北人嘱能。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像虱疏,于是被迫代替她去往敵國和親惹骂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評論 2 344