前言
若是對(duì)執(zhí)行隊(duì)列,宏任務(wù)肺樟,微任務(wù)的不太理解的,建議先閱讀
這一次逻淌,徹底弄懂 JavaScript 執(zhí)行機(jī)制(別還不知道什么是宏任務(wù)么伯,什么是微任務(wù))
你盼世界,我盼望你無(wú)bug
卡儒。Hello 大家好蹦狂!lesson
(聽說(shuō)封面和名字取得好就能把你騙進(jìn)來(lái) ??)
隱忍幾天為大家憋了個(gè)大招
這不,這一章節(jié)就是整理了45
道Promise
的筆試題讓大家爽一爽 ??朋贬。
其實(shí)想要寫一篇關(guān)于Promise
的文章是因?yàn)橹霸趯憚e的文章的時(shí)候被評(píng)論區(qū)的一名讀者無(wú)情的嘲諷了??:
"作者的Promise一定很爛"
所以編寫這么一個(gè)主題lesson我不是為了證明什么凯楔,而是想說(shuō):
"說(shuō)我爛我可以學(xué)啊"
另外查了很多關(guān)于Promise
的面試題,有些一上來(lái)就很難的锦募,有些連著幾篇題目都是一樣的摆屯,還有一些比較好的文章介紹的都是一些硬知識(shí)點(diǎn)。
這篇文章是一篇比較純的Promise
筆試文章,是我自己在做題的時(shí)候虐骑,根據(jù)題目想要的考點(diǎn)來(lái)反敲知識(shí)點(diǎn)准验,然后再由這個(gè)知識(shí)點(diǎn)編寫從淺到深的的題目。
所以你可以看到題目中有一些基礎(chǔ)題廷没,然后再?gòu)幕A(chǔ)題慢慢的變難糊饱,如果你看著感覺這段位配不上你的話,請(qǐng)答應(yīng)我堅(jiān)持看下去颠黎,會(huì)越來(lái)越難的...
咳咳另锋,循序漸進(jìn)嘛...
本文的題目沒有到特別深入,不過(guò)應(yīng)該覆蓋了大部分的考點(diǎn)狭归,另外為了不把大家繞混夭坪,答案也沒有考慮在Node
的執(zhí)行結(jié)果,執(zhí)行結(jié)果全為瀏覽器環(huán)境下过椎。因此如果你都會(huì)做的話室梅,可以盡情的在評(píng)論區(qū)再給我一個(gè)??
,放心疚宇,我脾氣很好的...
OK??亡鼠, 來(lái)看看通過(guò)閱讀本篇文章你可以學(xué)到:
- Promise的幾道基礎(chǔ)題
- Promise結(jié)合setTimeout
- Promise中的then、catch敷待、finally
- Promise中的all和race
- async/await的幾道題
- async處理錯(cuò)誤
- 綜合題
- 幾道大廠的面試題
前期準(zhǔn)備
在做下面??的題目之前拆宛,我希望你能清楚幾個(gè)知識(shí)點(diǎn)。
(如果你感覺一上來(lái)不想看這些列舉的知識(shí)點(diǎn)的話讼撒,直接看后面的例子再來(lái)理解它們也可以)
event loop
它的執(zhí)行順序:
- 一開始整個(gè)腳本作為一個(gè)宏任務(wù)執(zhí)行
- 執(zhí)行過(guò)程中同步代碼直接執(zhí)行浑厚,宏任務(wù)進(jìn)入宏任務(wù)隊(duì)列,微任務(wù)進(jìn)入微任務(wù)隊(duì)列
- 當(dāng)前宏任務(wù)執(zhí)行完出隊(duì)根盒,檢查微任務(wù)列表钳幅,有則依次執(zhí)行,直到全部執(zhí)行完
- 執(zhí)行瀏覽器UI線程的渲染工作
- 檢查是否有
Web Worker
任務(wù)炎滞,有則執(zhí)行 - 執(zhí)行完本輪的宏任務(wù)敢艰,回到2,依此循環(huán)册赛,直到宏任務(wù)和微任務(wù)隊(duì)列都為空
微任務(wù)包括:MutationObserver
钠导、Promise.then()或reject()
、Promise為基礎(chǔ)開發(fā)的其它技術(shù)森瘪,比如fetch API
牡属、V8
的垃圾回收過(guò)程、Node獨(dú)有的process.nextTick
扼睬。
宏任務(wù)包括:script
逮栅、script
、setTimeout
、setInterval
措伐、setImmediate
特纤、I/O
、UI rendering
侥加。
注意??:在所有任務(wù)開始的時(shí)候捧存,由于宏任務(wù)中包括了script
,所以瀏覽器會(huì)先執(zhí)行一個(gè)宏任務(wù)担败,在這個(gè)過(guò)程中你看到的延遲任務(wù)(例如setTimeout
)將被放到下一輪宏任務(wù)中來(lái)執(zhí)行昔穴。
1. Promise的幾道基礎(chǔ)題
1.1 題目一
const promise1 = new Promise((resolve, reject) => {
console.log('promise1'
)})
console.log('1', promise1);
過(guò)程分析:
- 從上至下,先遇到
new Promise
氢架,執(zhí)行該構(gòu)造函數(shù)中的代碼promise1
- 然后執(zhí)行同步代碼
1
傻咖,此時(shí)promise1
沒有被resolve
或者reject
朋魔,因此狀態(tài)還是pending
結(jié)果:
'promise1''1' Promise{<pending>}
1.2 題目二
const promise = new Promise((resolve, reject) => {
console.log(1);
resolve('success')
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
過(guò)程分析:
- 從上至下岖研,先遇到
new Promise
,執(zhí)行其中的同步代碼1
- 再遇到
resolve('success')
警检, 將promise
的狀態(tài)改為了resolved
并且將值保存下來(lái) - 繼續(xù)執(zhí)行同步代碼
2
- 跳出
promise
孙援,往下執(zhí)行,碰到promise.then
這個(gè)微任務(wù)扇雕,將其加入微任務(wù)隊(duì)列 - 執(zhí)行同步代碼
4
- 本輪宏任務(wù)全部執(zhí)行完畢拓售,檢查微任務(wù)隊(duì)列,發(fā)現(xiàn)
promise.then
這個(gè)微任務(wù)且狀態(tài)為resolved
镶奉,執(zhí)行它础淤。
結(jié)果:
1 2 4 3
1.3 題目三
const promise = new Promise((resolve, reject) => {
console.log(1);
console.log(2);
});
promise.then(() => { console.log(3);});console.log(4);
過(guò)程分析
- 和題目二相似,只不過(guò)在
promise
中并沒有resolve
或者reject
- 因此
promise.then
并不會(huì)執(zhí)行哨苛,它只有在被改變了狀態(tài)之后才會(huì)執(zhí)行鸽凶。
結(jié)果:
1 2 4
1.4 題目四
const promise1 = new Promise((resolve, reject) => {
console.log('promise1')
resolve('resolve1')
})
const promise2 = promise1.then(res => {
console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);
過(guò)程分析:
- 從上至下,先遇到
new Promise
建峭,執(zhí)行該構(gòu)造函數(shù)中的代碼promise1
- 碰到
resolve
函數(shù), 將promise1
的狀態(tài)改變?yōu)?code>resolved, 并將結(jié)果保存下來(lái) - 碰到
promise1.then
這個(gè)微任務(wù)玻侥,將它放入微任務(wù)隊(duì)列 -
promise2
是一個(gè)新的狀態(tài)為pending
的Promise
- 執(zhí)行同步代碼
1
, 同時(shí)打印出promise1
的狀態(tài)是resolved
- 執(zhí)行同步代碼
2
亿蒸,同時(shí)打印出promise2
的狀態(tài)是pending
- 宏任務(wù)執(zhí)行完畢凑兰,查找微任務(wù)隊(duì)列,發(fā)現(xiàn)
promise1.then
這個(gè)微任務(wù)且狀態(tài)為resolved
边锁,執(zhí)行它姑食。
結(jié)果:
'promise1''1' Promise{<resolved>: 'resolve1'}'2' Promise{<pending>}'resolve1'
1.5 題目五
接下來(lái)看看這道題:
const fn = () => (new Promise((resolve, reject) => {
console.log(1);
resolve('success')
}))
fn().then(res => {
console.log(res)
})
console.log('start')
這道題里最先執(zhí)行的是'start'
嗎 ??? ?
請(qǐng)仔細(xì)看看哦茅坛,fn
函數(shù)它是直接返回了一個(gè)new Promise
的矢门,而且fn
函數(shù)的調(diào)用是在start
之前,所以它里面的內(nèi)容應(yīng)該會(huì)先執(zhí)行。
結(jié)果:
1'start''success'
1.6 題目六
如果把fn
的調(diào)用放到start
之后呢祟剔?
const fn = () =>
new Promise((resolve, reject) => {
console.log(1);
resolve("success");
});
console.log("start");
fn().then(res => {
console.log(res);
});
是的隔躲,現(xiàn)在start
就在1
之前打印出來(lái)了,因?yàn)?code>fn函數(shù)是之后執(zhí)行的物延。
注意??:之前我們很容易就以為看到new Promise()就執(zhí)行它的第一個(gè)參數(shù)函數(shù)了宣旱,其實(shí)這是不對(duì)的,就像這兩道題中叛薯,我們得注意它是不是被包裹在函數(shù)當(dāng)中浑吟,如果是的話,只有在函數(shù)調(diào)用的時(shí)候才會(huì)執(zhí)行耗溜。
答案:
"start"1"success"
好嘞组力,學(xué)完了這幾道基礎(chǔ)題,讓我們來(lái)用個(gè)表情包壓壓驚抖拴。
2. Promise結(jié)合setTimeout
2.1 題目一
console.log('start')
setTimeout(() => {
console.log('time')
})
Promise.resolve().then(() => {
console.log('resolve')
})
console.log('end
過(guò)程分析:
- 剛開始整個(gè)腳本作為一個(gè)宏任務(wù)來(lái)執(zhí)行燎字,對(duì)于同步代碼直接壓入執(zhí)行棧進(jìn)行執(zhí)行,因此先打印出
start
和end
阿宅。 -
setTimout
作為一個(gè)宏任務(wù)被放入宏任務(wù)隊(duì)列(下一個(gè)) -
Promise.then
作為一個(gè)微任務(wù)被放入微任務(wù)隊(duì)列 - 本次宏任務(wù)執(zhí)行完候衍,檢查微任務(wù),發(fā)現(xiàn)
Promise.then
洒放,執(zhí)行它 - 接下來(lái)進(jìn)入下一個(gè)宏任務(wù)蛉鹿,發(fā)現(xiàn)
setTimeout
,執(zhí)行往湿。
結(jié)果:
'start''end''resolve''time'
2.2 題目二
const promise = new Promise((resolve, reject) => {
console.log(1);
setTimeout(() => {
console.log("timerStart");
resolve("success");
console.log("timerEnd");
}, 0);
console.log(2);
});
promise.then((res) => {
console.log(res);
});
console.log(4);
過(guò)程分析:
和題目1.2
很像妖异,不過(guò)在resolve
的外層加了一層setTimeout
定時(shí)器。
- 從上至下领追,先遇到
new Promise
他膳,執(zhí)行該構(gòu)造函數(shù)中的代碼1
- 然后碰到了定時(shí)器,將這個(gè)定時(shí)器中的函數(shù)放到下一個(gè)宏任務(wù)的延遲隊(duì)列中等待執(zhí)行
- 執(zhí)行同步代碼
2
- 跳出
promise
函數(shù)蔓腐,遇到promise.then
矩乐,但其狀態(tài)還是為pending
,這里理解為先不執(zhí)行 - 執(zhí)行同步代碼
4
- 一輪循環(huán)過(guò)后回论,進(jìn)入第二次宏任務(wù)散罕,發(fā)現(xiàn)延遲隊(duì)列中有
setTimeout
定時(shí)器,執(zhí)行它 - 首先執(zhí)行
timerStart
傀蓉,然后遇到了resolve
欧漱,將promise
的狀態(tài)改為resolved
且保存結(jié)果并將之前的promise.then
推入微任務(wù)隊(duì)列 - 繼續(xù)執(zhí)行同步代碼
timerEnd
- 宏任務(wù)全部執(zhí)行完畢,查找微任務(wù)隊(duì)列葬燎,發(fā)現(xiàn)
promise.then
這個(gè)微任務(wù)误甚,執(zhí)行它缚甩。
因此執(zhí)行結(jié)果為:
124"timerStart""timerEnd""success"
2.3 題目三
題目三分了兩個(gè)題目,因?yàn)榭粗疾畈欢嘁ぐ睿贿^(guò)執(zhí)行的結(jié)果卻不一樣擅威,大家不妨先猜猜下面兩個(gè)題目分別執(zhí)行什么:
(1):
setTimeout(() => {
console.log('timer1');
setTimeout(() => {
console.log('timer3')
}, 0)
}, 0)
setTimeout(() => {
console.log('timer2')
}, 0)
console.log('start')
(2):
setTimeout(() => {
console.log('timer1');
Promise.resolve().then(() => {
console.log('promise')
})
}, 0)
setTimeout(() => {
console.log('timer2')
}, 0)
console.log('start')
執(zhí)行結(jié)果:
'start''timer1''timer2''timer3'
'start''timer1''promise''timer2'
這兩個(gè)例子,看著好像只是把第一個(gè)定時(shí)器中的內(nèi)容換了一下而已冈钦。
一個(gè)是為定時(shí)器timer3
郊丛,一個(gè)是為Promise.then
但是如果是定時(shí)器timer3
的話,它會(huì)在timer2
后執(zhí)行瞧筛,而Promise.then
卻是在timer2
之前執(zhí)行厉熟。
你可以這樣理解,Promise.then
是微任務(wù)较幌,它會(huì)被加入到本輪中的微任務(wù)列表揍瑟,而定時(shí)器timer3
是宏任務(wù),它會(huì)被加入到下一輪的宏任務(wù)中乍炉。
理解完這兩個(gè)案例绢片,可以來(lái)看看下面一道比較難的題目了。
2.3 題目三
Promise.resolve().then(() => {
console.log('promise1');
const timer2 = setTimeout(() => {
console.log('timer2')
}, 0)
});
const timer1 = setTimeout(() => {
console.log('timer1')
Promise.resolve().then(() => {
console.log('promise2')
})
}, 0)
console.log('start');
這道題稍微的難一些恩急,在promise
中執(zhí)行定時(shí)器杉畜,又在定時(shí)器中執(zhí)行promise
纪蜒;
并且要注意的是衷恭,這里的Promise
是直接resolve
的,而之前的new Promise
不一樣纯续。
因此過(guò)程分析為:
- 剛開始整個(gè)腳本作為第一次宏任務(wù)來(lái)執(zhí)行随珠,我們將它標(biāo)記為宏1,從上至下執(zhí)行
- 遇到
Promise.resolve().then
這個(gè)微任務(wù)猬错,將then
中的內(nèi)容加入第一次的微任務(wù)隊(duì)列標(biāo)記為微1 - 遇到定時(shí)器
timer1
窗看,將它加入下一次宏任務(wù)的延遲列表,標(biāo)記為宏2倦炒,等待執(zhí)行(先不管里面是什么內(nèi)容) - 執(zhí)行宏1中的同步代碼
start
- 第一次宏任務(wù)(宏1)執(zhí)行完畢显沈,檢查第一次的微任務(wù)隊(duì)列(微1),發(fā)現(xiàn)有一個(gè)
promise.then
這個(gè)微任務(wù)需要執(zhí)行 - 執(zhí)行打印出微1中同步代碼
promise1
逢唤,然后發(fā)現(xiàn)定時(shí)器timer2
拉讯,將它加入宏2的后面,標(biāo)記為宏3 - 第一次微任務(wù)隊(duì)列(微1)執(zhí)行完畢鳖藕,執(zhí)行第二次宏任務(wù)(宏2)魔慷,首先執(zhí)行同步代碼
timer1
- 然后遇到了
promise2
這個(gè)微任務(wù),將它加入此次循環(huán)的微任務(wù)隊(duì)列著恩,標(biāo)記為微2 -
宏2中沒有同步代碼可執(zhí)行了院尔,查找本次循環(huán)的微任務(wù)隊(duì)列(微2)蜻展,發(fā)現(xiàn)了
promise2
,執(zhí)行它 - 第二輪執(zhí)行完畢邀摆,執(zhí)行宏3纵顾,打印出
timer2
所以結(jié)果為:
'start''promise1''timer1''promise2''timer2'
如果感覺有點(diǎn)繞的話,可以看下面這張圖栋盹,就一目了然了片挂。
2.4 題目四
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
})
const promise2 = promise1.then(() => {
throw new Error('error!!!')
})
console.log('promise1', promise1)
console.log('promise2', promise2)
setTimeout(() => {
console.log('promise1', promise1)
console.log('promise2', promise2)
}, 2000)
過(guò)程分析:
- 從上至下,先執(zhí)行第一個(gè)
new Promise
中的函數(shù)贞盯,碰到setTimeout
將它加入下一個(gè)宏任務(wù)列表 - 跳出
new Promise
音念,碰到promise1.then
這個(gè)微任務(wù),但其狀態(tài)還是為pending
躏敢,這里理解為先不執(zhí)行 -
promise2
是一個(gè)新的狀態(tài)為pending
的Promise
- 執(zhí)行同步代碼
console.log('promise1')
闷愤,且打印出的promise1
的狀態(tài)為pending
- 執(zhí)行同步代碼
console.log('promise2')
,且打印出的promise2
的狀態(tài)為pending
- 碰到第二個(gè)定時(shí)器件余,將其放入下一個(gè)宏任務(wù)列表
- 第一輪宏任務(wù)執(zhí)行結(jié)束讥脐,并且沒有微任務(wù)需要執(zhí)行,因此執(zhí)行第二輪宏任務(wù)
- 先執(zhí)行第一個(gè)定時(shí)器里的內(nèi)容啼器,將
promise1
的狀態(tài)改為resolved
且保存結(jié)果并將之前的promise1.then
推入微任務(wù)隊(duì)列 - 該定時(shí)器中沒有其它的同步代碼可執(zhí)行旬渠,因此執(zhí)行本輪的微任務(wù)隊(duì)列,也就是
promise1.then
端壳,它拋出了一個(gè)錯(cuò)誤告丢,且將promise2
的狀態(tài)設(shè)置為了rejected
- 第一個(gè)定時(shí)器執(zhí)行完畢,開始執(zhí)行第二個(gè)定時(shí)器中的內(nèi)容
- 打印出
'promise1'
损谦,且此時(shí)promise1
的狀態(tài)為resolved
- 打印出
'promise2'
岖免,且此時(shí)promise2
的狀態(tài)為rejected
完整的結(jié)果為:
'promise1' Promise{<pending>}
'promise2' Promise{<pending>}
test5.html:102 Uncaught (in promise) Error: error!!! at test.html:102
'promise1' Promise{<resolved>: "success"}
'promise2' Promise{<rejected>: Error: error!!!}
2.5 題目五
如果你上面這道題搞懂了之后,我們就可以來(lái)做做這道了照捡,你應(yīng)該能很快就給出答案:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("success");
console.log("timer1");
}, 1000);
console.log("promise1里的內(nèi)容");
});
const promise2 = promise1.then(() => {
throw new Error("error!!!");
});
console.log("promise1", promise1);
console.log("promise2", promise2);
setTimeout(() => {
console.log("timer2");
console.log("promise1", promise1);
console.log("promise2", promise2);
}, 2000);
結(jié)果:
'promise1里的內(nèi)容'
'promise1' Promise{<pending>}
'promise2' Promise{<pending>}
'timer1'
test5.html:102 Uncaught (in promise) Error: error!!! at test.html:102
'timer2'
'promise1' Promise{<resolved>: "success"}
'promise2' Promise{<rejected>: Error: error!!!}
3. Promise中的then颅湘、catch、finally
額栗精,可能你看到下面??這么多的1闯参,2,3
脾氣就上來(lái)了悲立,不是說(shuō)好了本篇文章沒什么屁話嘛鹿寨,怎么還是這么多一二三四。
??级历,你要理解我的用心良苦啊释移,我這是幫你把知識(shí)點(diǎn)都列舉出來(lái),做個(gè)總結(jié)而已寥殖。當(dāng)然玩讳,你也可以先不看涩蜘,先去做后面的題,然后再回過(guò)頭來(lái)看這些熏纯,你就覺得這些點(diǎn)都好好懂啊同诫,甚至都不需要記。
總結(jié):
-
Promise
的狀態(tài)一經(jīng)改變就不能再改變樟澜。(見3.1) -
.then
和.catch
都會(huì)返回一個(gè)新的Promise
误窖。(上面的??1.4證明了) -
catch
不管被連接到哪里,都能捕獲上層的錯(cuò)誤秩贰。(見3.2) - 在
Promise
中霹俺,返回任意一個(gè)非promise
的值都會(huì)被包裹成promise
對(duì)象,例如return 2
會(huì)被包裝為return Promise.resolve(2)
毒费。 -
Promise
的.then
或者.catch
可以被調(diào)用多次, 當(dāng)如果Promise
內(nèi)部的狀態(tài)一經(jīng)改變丙唧,并且有了一個(gè)值,那么后續(xù)每次調(diào)用.then
或者.catch
的時(shí)候都會(huì)直接拿到該值觅玻。(見3.5) -
.then
或者.catch
中return
一個(gè)error
對(duì)象并不會(huì)拋出錯(cuò)誤想际,所以不會(huì)被后續(xù)的.catch
捕獲。(見3.6) -
.then
或.catch
返回的值不能是 promise 本身溪厘,否則會(huì)造成死循環(huán)胡本。(見3.7) -
.then
或者.catch
的參數(shù)期望是函數(shù),傳入非函數(shù)則會(huì)發(fā)生值穿透畸悬。(見3.8) -
.then
方法是能接收兩個(gè)參數(shù)的侧甫,第一個(gè)是處理成功的函數(shù),第二個(gè)是處理失敗的函數(shù)傻昙,再某些時(shí)候你可以認(rèn)為catch
是.then
第二個(gè)參數(shù)的簡(jiǎn)便寫法闺骚。(見3.9) -
.finally
方法也是返回一個(gè)Promise
彩扔,他在Promise
結(jié)束的時(shí)候妆档,無(wú)論結(jié)果為resolved
還是rejected
,都會(huì)執(zhí)行里面的回調(diào)函數(shù)虫碉。
3.1 題目一
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);
})
構(gòu)造函數(shù)中的 resolve
或 reject
只有第一次執(zhí)行有效贾惦,多次調(diào)用沒有任何作用 。驗(yàn)證了第一個(gè)結(jié)論敦捧,Promise
的狀態(tài)一經(jīng)改變就不能再改變须板。
3.2 題目二
const promise = new Promise((resolve, reject) => {
reject("error");
resolve("success2");
});
promise
.then(res => {
console.log("then: ", res);
}).then(res => {
console.log("then: ", res);
}).catch(err => {
console.log("catch: ", err);
}).then(res => {
console.log("then: ", res);
})
結(jié)果:
"catch: " "error""then3: " undefined
驗(yàn)證了第三個(gè)結(jié)論,catch
不管被連接到哪里兢卵,都能捕獲上層的錯(cuò)誤习瑰。
3.3 題目三
Promise.resolve(1)
.then(res => {
console.log(res);
return 2;
})
.catch(err => {
return 3;
})
.then(res => {
console.log(res);
});
結(jié)果:
12
Promise
可以鏈?zhǔn)秸{(diào)用,不過(guò)promise
每次調(diào)用 .then
或者 .catch
都會(huì)返回一個(gè)新的 promise
秽荤,從而實(shí)現(xiàn)了鏈?zhǔn)秸{(diào)用, 它并不像一般我們?nèi)蝿?wù)的鏈?zhǔn)秸{(diào)用一樣return this
甜奄。
上面的輸出結(jié)果之所以依次打印出1
和2
,那是因?yàn)?code>resolve(1)之后走的是第一個(gè)then
方法,并沒有走catch
里监徘,所以第二個(gè)then
中的res
得到的實(shí)際上是第一個(gè)then
的返回值藏古。
且return 2
會(huì)被包裝成resolve(2)
。
3.4 題目四
如果把3.3
中的Promise.resolve(1)
改為Promise.reject(1)
又會(huì)怎么樣呢烟阐?
Promise.reject(1)
.then(res => {
console.log(res);
return 2;
})
.catch(err => {
console.log(err);
return 3
})
.then(res => {
console.log(res);
});
結(jié)果:
13
結(jié)果打印的當(dāng)然是 1 和 3
啦搬俊,因?yàn)?code>reject(1)此時(shí)走的就是catch
,且第二個(gè)then
中的res
得到的就是catch
中的返回值蜒茄。
3.5 題目五
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('timer')
resolve('success')
}, 1000)
})
const start = Date.now();
promise.then(res => {
console.log(res, Date.now() - start)
})
promise.then(res => {
console.log(res, Date.now() - start)
})
執(zhí)行結(jié)果:
'timer'success 1001success 1002
當(dāng)然唉擂,如果你足夠快的話,也可能兩個(gè)都是1001
檀葛。
Promise
的 .then
或者 .catch
可以被調(diào)用多次楔敌,但這里 Promise
構(gòu)造函數(shù)只執(zhí)行一次∽ぷ唬或者說(shuō) promise
內(nèi)部狀態(tài)一經(jīng)改變卵凑,并且有了一個(gè)值,那么后續(xù)每次調(diào)用 .then
或者 .catch
都會(huì)直接拿到該值胜臊。
3.6 題目六
Promise.resolve().then(() => {
return new Error('error!!!')
}).then(res => {
console.log("then: ", res)
}).catch(err => {
console.log("catch: ", err)
})
猜猜這里的結(jié)果輸出的是什么 ??? 勺卢?
你可能想到的是進(jìn)入.catch
然后被捕獲了錯(cuò)誤。
結(jié)果并不是這樣的象对,它走的是.then
里面:
"then: " "Error: error!!!"
這也驗(yàn)證了第4點(diǎn)和第6點(diǎn)黑忱,返回任意一個(gè)非 promise
的值都會(huì)被包裹成 promise
對(duì)象,因此這里的return new Error('error!!!')
也被包裹成了return Promise.resolve(new Error('error!!!'))
勒魔。
當(dāng)然如果你拋出一個(gè)錯(cuò)誤的話甫煞,可以用下面??兩的任意一種:
return Promise.reject(new Error('error!!!'));// orthrow new Error('error!!!')
3.7 題目七
const promise = Promise.resolve().then(() => { return promise;})promise.catch(console.err)
.then
或 .catch
返回的值不能是 promise 本身,否則會(huì)造成死循環(huán)冠绢。
因此結(jié)果會(huì)報(bào)錯(cuò):
Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
3.8 題目八
Promise.resolve(1) .then(2) .then(Promise.resolve(3)) .then(console.log)
這道題看著好像很簡(jiǎn)單抚吠,又感覺很復(fù)雜的樣子,怎么這么多個(gè).then
啊... ??
其實(shí)你只要記住原則8:.then
或者 .catch
的參數(shù)期望是函數(shù)弟胀,傳入非函數(shù)則會(huì)發(fā)生值穿透楷力。
第一個(gè)then
和第二個(gè)then
中傳入的都不是函數(shù),一個(gè)是數(shù)字類型孵户,一個(gè)是對(duì)象類型萧朝,因此發(fā)生了穿透,將resolve(1)
的值直接傳到最后一個(gè)then
里夏哭。
所以輸出結(jié)果為:
1
3.9 題目九
下面來(lái)介紹一下.then
函數(shù)中的兩個(gè)參數(shù)检柬。
第一個(gè)參數(shù)是用來(lái)處理Promise
成功的函數(shù),第二個(gè)則是處理失敗的函數(shù)竖配。
也就是說(shuō)Promise.resolve('1')
的值會(huì)進(jìn)入成功的函數(shù)何址,Promise.reject('2')
的值會(huì)進(jìn)入失敗的函數(shù)酱固。
讓我們來(lái)看看這個(gè)例子??:
Promise.reject('err!!!')
.then((res) => {
console.log('success', res)
}, (err) => {
console.log('error', err)
}).catch(err => {
console.log('catch', err)
})
這里的執(zhí)行結(jié)果是:
'error' 'error!!!'
它進(jìn)入的是then()
中的第二個(gè)參數(shù)里面,而如果把第二個(gè)參數(shù)去掉头朱,就進(jìn)入了catch()
中:
Promise.reject('err!!!')
.then((res) => {
console.log('success', res)
}).catch(err => {
console.log('catch', err)
})
執(zhí)行結(jié)果:
'catch' 'error!!!'
但是有一個(gè)問(wèn)題运悲,如果是這個(gè)案例呢?
Promise.resolve()
.then(function success (res) {
throw new Error('error!!!')
}, function fail1 (err) {
console.log('fail1', err)
}).catch(function fail2 (err) {
console.log('fail2', err)
})
由于Promise
調(diào)用的是resolve()
项钮,因此.then()
執(zhí)行的應(yīng)該是success()
函數(shù)班眯,可是success()
函數(shù)拋出的是一個(gè)錯(cuò)誤,它會(huì)被后面的catch()
給捕獲到烁巫,而不是被fail1
函數(shù)捕獲署隘。
因此執(zhí)行結(jié)果為:
fail2 Error: error!!! at success
3.10 題目十
接著來(lái)看看.finally()
,這個(gè)功能一般不太用在面試中亚隙,不過(guò)如果碰到了你也應(yīng)該知道該如何處理磁餐。
其實(shí)你只要記住它三個(gè)很重要的知識(shí)點(diǎn)就可以了:
-
.finally()
方法不管Promise
對(duì)象最后的狀態(tài)如何都會(huì)執(zhí)行 -
.finally()
方法的回調(diào)函數(shù)不接受任何的參數(shù),也就是說(shuō)你在.finally()
函數(shù)中是沒法知道Promise
最終的狀態(tài)是resolved
還是rejected
的 - 它最終返回的默認(rèn)會(huì)是一個(gè)原來(lái)的Promise對(duì)象值阿弃,不過(guò)如果拋出的是一個(gè)異常則返回異常的
Promise
對(duì)象诊霹。
來(lái)看看這個(gè)簡(jiǎn)單的例子??:
Promise.resolve('1')
.then(res => {
console.log(res)
})
.finally(() => {
console.log('finally')
})
Promise.resolve('2')
.finally(() => {
console.log('finally2')
return '我是finally2返回的值'
})
.then(res => {
console.log('finally2后面的then函數(shù)', res)
})
這兩個(gè)Promise
的.finally
都會(huì)執(zhí)行,且就算finally2
返回了新的值渣淳,它后面的then()
函數(shù)接收到的結(jié)果卻還是'2'
脾还,因此打印結(jié)果為:
'1''finally2''finally''finally2后面的then函數(shù)' '2'
至于為什么finally2
的打印要在finally
前面,請(qǐng)看下一個(gè)例子中的解析入愧。
不過(guò)在此之前讓我們?cè)賮?lái)確認(rèn)一下鄙漏,finally
中要是拋出的是一個(gè)異常是怎樣的:
Promise.resolve('1')
.finally(() => {
console.log('finally1')
throw new Error('我是finally中拋出的異常')
})
.then(res => {
console.log('finally后面的then函數(shù)', res)
})
.catch(err => {
console.log('捕獲錯(cuò)誤', err)
})
執(zhí)行結(jié)果為:
'finally1''捕獲錯(cuò)誤' Error: 我是finally中拋出的異常
但是如果改為return new Error('我是finally中拋出的異常')
,打印出來(lái)的就是'finally后面的then函數(shù) 1'
OK棺蛛,??怔蚌,讓我們來(lái)看一個(gè)比較難的例子??:
function promise1 () {
let p = new Promise((resolve) => {
console.log('promise1');
resolve('1')
})
return p;
}
function promise2 () {
return new Promise((resolve, reject) => {
reject('error')
})
}
promise1()
.then(res => console.log(res))
.catch(err => console.log(err))
.finally(() => console.log('finally1'))
promise2()
.then(res => console.log(res))
.catch(err => console.log(err))
.finally(() => console.log('finally2'))
執(zhí)行過(guò)程:
- 首先定義了兩個(gè)函數(shù)
promise1
和promise2
,先不管接著往下看旁赊。 -
promise1
函數(shù)先被調(diào)用了桦踊,然后執(zhí)行里面new Promise
的同步代碼打印出promise1
- 之后遇到了
resolve(1)
,將p
的狀態(tài)改為了resolved
并將結(jié)果保存下來(lái)彤恶。 - 此時(shí)
promise1
內(nèi)的函數(shù)內(nèi)容已經(jīng)執(zhí)行完了钞钙,跳出該函數(shù) - 碰到了
promise1().then()
,由于promise1
的狀態(tài)已經(jīng)發(fā)生了改變且為resolved
因此將promise1().then()
這條微任務(wù)加入本輪的微任務(wù)列表(這是第一個(gè)微任務(wù)) - 這時(shí)候要注意了声离,代碼并不會(huì)接著往鏈?zhǔn)秸{(diào)用的下面走,也就是不會(huì)先將
.finally
加入微任務(wù)列表瘫怜,那是因?yàn)?code>.then本身就是一個(gè)微任務(wù)术徊,它鏈?zhǔn)胶竺娴膬?nèi)容必須得等當(dāng)前這個(gè)微任務(wù)執(zhí)行完才會(huì)執(zhí)行,因此這里我們先不管.finally()
- 再往下走碰到了
promise2()
函數(shù)鲸湃,其中返回的new Promise
中并沒有同步代碼需要執(zhí)行赠涮,所以執(zhí)行reject('error')
的時(shí)候?qū)?code>promise2函數(shù)中的Promise
的狀態(tài)變?yōu)榱?code>rejected - 跳出
promise2
函數(shù)子寓,遇到了promise2().then()
,將其加入當(dāng)前的微任務(wù)隊(duì)列(這是第二個(gè)微任務(wù))笋除,且鏈?zhǔn)秸{(diào)用后面的內(nèi)容得等該任務(wù)執(zhí)行完后才執(zhí)行斜友,和.then()
一樣。 - OK垃它, 本輪的宏任務(wù)全部執(zhí)行完了鲜屏,來(lái)看看微任務(wù)列表,存在
promise1().then()
国拇,執(zhí)行它洛史,打印出1
,然后遇到了.finally()
這個(gè)微任務(wù)將它加入微任務(wù)列表(這是第三個(gè)微任務(wù))等待執(zhí)行 - 再執(zhí)行
promise2().catch()
打印出error
酱吝,執(zhí)行完后將finally2
加入微任務(wù)加入微任務(wù)列表(這是第四個(gè)微任務(wù)) - OK也殖, 本輪又全部執(zhí)行完了,但是微任務(wù)列表還有兩個(gè)新的微任務(wù)沒有執(zhí)行完务热,因此依次執(zhí)行
finally1
和finally2
忆嗜。
結(jié)果:
'promise1''1''error''finally1''finally2'
在這道題中其實(shí)能擴(kuò)展的東西挺多的,之前沒有提到崎岂,那就是你可以理解為鏈?zhǔn)秸{(diào)用后面的內(nèi)容需要等前一個(gè)調(diào)用執(zhí)行完才會(huì)執(zhí)行霎褐。
就像是這里的finally()
會(huì)等promise1().then()
執(zhí)行完才會(huì)將finally()
加入微任務(wù)隊(duì)列,其實(shí)如果這道題中你把finally()
換成是then()
也是這樣的:
function promise1 () {
let p = new Promise((resolve) => {
console.log('promise1');
resolve('1')
})
return p;
}
function promise2 () {
return new Promise((resolve, reject) => {
reject('error')
})
}
promise1()
.then(res => console.log(res))
.catch(err => console.log(err))
.then(() => console.log('finally1'))
promise2()
.then(res => console.log(res))
.catch(err => console.log(err))
.then(() => console.log('finally2'))
4. Promise中的all和race
在做下面??的題目之前该镣,讓我們先來(lái)了解一下Promise.all()
和Promise.race()
的用法冻璃。
通俗來(lái)說(shuō),.all()
的作用是接收一組異步任務(wù)损合,然后并行執(zhí)行異步任務(wù)省艳,并且在所有異步操作執(zhí)行完后才執(zhí)行回調(diào)。
.race()
的作用也是接收一組異步任務(wù)嫁审,然后并行執(zhí)行異步任務(wù)跋炕,只保留取第一個(gè)執(zhí)行完成的異步操作的結(jié)果,其他的方法仍在執(zhí)行律适,不過(guò)執(zhí)行結(jié)果會(huì)被拋棄辐烂。
來(lái)看看題目一。
4.1 題目一
我們知道如果直接在腳本文件中定義一個(gè)Promise
捂贿,它構(gòu)造函數(shù)的第一個(gè)參數(shù)是會(huì)立即執(zhí)行的纠修,就像這樣:
const p1 = new Promise(r => console.log('立即打印'))
控制臺(tái)中會(huì)立即打印出 “立即打印”。
因此為了控制它什么時(shí)候執(zhí)行厂僧,我們可以用一個(gè)函數(shù)包裹著它扣草,在需要它執(zhí)行的時(shí)候,調(diào)用這個(gè)函數(shù)就可以了:
function runP1 () {
const p1 = new Promise(r => console.log('立即打印'))
return p1
}
runP1() // 調(diào)用此函數(shù)時(shí)才執(zhí)行
OK ??, 讓我們回歸正題辰妙。
現(xiàn)在來(lái)構(gòu)建這么一個(gè)函數(shù):
function runAsync (x) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
return p
}
該函數(shù)傳入一個(gè)值x
鹰祸,然后間隔一秒后打印出這個(gè)x
。
如果我用.all()
來(lái)執(zhí)行它會(huì)怎樣呢密浑?
function runAsync (x) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
return p
}
Promise.all([runAsync(1), runAsync(2), runAsync(3)])
.then(res => console.log(res))
先來(lái)想想此段代碼在瀏覽器中會(huì)如何執(zhí)行蛙婴?
沒錯(cuò),當(dāng)你打開頁(yè)面的時(shí)候尔破,在間隔一秒后街图,控制臺(tái)會(huì)同時(shí)打印出1, 2, 3
,還有一個(gè)數(shù)組[1, 2, 3]
呆瞻。
123[1, 2, 3]
所以你現(xiàn)在能理解這句話的意思了嗎:有了all台夺,你就可以并行執(zhí)行多個(gè)異步操作,并且在一個(gè)回調(diào)中處理所有的返回?cái)?shù)據(jù)痴脾。
.all()
后面的.then()
里的回調(diào)函數(shù)接收的就是所有異步操作的結(jié)果颤介。
而且這個(gè)結(jié)果中數(shù)組的順序和Promise.all()
接收到的數(shù)組順序一致!T蘩怠滚朵!
??
有一個(gè)場(chǎng)景是很適合用這個(gè)的,一些游戲類的素材比較多的應(yīng)用前域,打開網(wǎng)頁(yè)時(shí)辕近,預(yù)先加載需要用到的各種資源如圖片、flash以及各種靜態(tài)文件匿垄。所有的都加載完后移宅,我們?cè)龠M(jìn)行頁(yè)面的初始化。
”
4.2 題目二
我新增了一個(gè)runReject
函數(shù)椿疗,它用來(lái)在1000 * x
秒后reject
一個(gè)錯(cuò)誤漏峰。
同時(shí).catch()
函數(shù)能夠捕獲到.all()
里最先的那個(gè)異常,并且只執(zhí)行一次届榄。
想想這道題會(huì)怎樣執(zhí)行呢 ???浅乔?
function runAsync (x) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
return p
}
function runReject (x) {
const p = new Promise((res, rej) => setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x))
return p
}
Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)])
.then(res => console.log(res))
.catch(err => console.log(err))
不賣關(guān)子了 ??,讓我來(lái)公布答案:
13// 2s后輸出2Error: 2// 4s后輸出4
沒錯(cuò)铝条,就像我之前說(shuō)的靖苇,.catch
是會(huì)捕獲最先的那個(gè)異常,在這道題目中最先的異常就是runReject(2)
的結(jié)果班缰。
另外贤壁,如果一組異步操作中有一個(gè)異常都不會(huì)進(jìn)入.then()
的第一個(gè)回調(diào)函數(shù)參數(shù)中。
注意鲁捏,為什么不說(shuō)是不進(jìn)入.then()
中呢 ???芯砸?
哈哈萧芙,大家別忘了.then()
方法的第二個(gè)參數(shù)也是可以捕獲錯(cuò)誤的:
Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)])
.then(res => console.log(res),
err => console.log(err))
4.3 題目三
接下來(lái)讓我們看看另一個(gè)有趣的方法.race
给梅。
讓我看看你們的英語(yǔ)水平如何假丧?
快!一秒鐘告訴我race
是什么意思动羽?
好吧...你們果然很強(qiáng)...
race
包帚,比賽,賽跑的意思运吓。
所以使用.race()
方法渴邦,它只會(huì)獲取最先執(zhí)行完成的那個(gè)結(jié)果,其它的異步任務(wù)雖然也會(huì)繼續(xù)進(jìn)行下去拘哨,不過(guò)race
已經(jīng)不管那些任務(wù)的結(jié)果了谋梭。
來(lái),改造一下4.1
這道題:
function runAsync (x) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
return p
}
Promise.race([runAsync(1), runAsync(2), runAsync(3)])
.then(res => console.log('result: ', res))
.catch(err => console.log(err))
執(zhí)行結(jié)果為:
1
result: 1
2
3
??
這個(gè)race有什么用呢倦青?使用場(chǎng)景還是很多的瓮床,比如我們可以用race給某個(gè)異步請(qǐng)求設(shè)置超時(shí)時(shí)間,并且在超時(shí)后執(zhí)行相應(yīng)的操作
”
4.4 題目四
改造一下題目4.2
:
function runAsync(x) {
const p = new Promise(r =>
setTimeout(() => r(x, console.log(x)), 1000)
);
return p;
}
function runReject(x) {
const p = new Promise((res, rej) =>
setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x)
);
return p;
}
Promise.race([runReject(0), runAsync(1), runAsync(2), runAsync(3)])
.then(res => console.log("result: ", res))
.catch(err => console.log(err));
遇到錯(cuò)誤的話产镐,也是一樣的隘庄,在這道題中,runReject(0)
最先執(zhí)行完癣亚,所以進(jìn)入了catch()
中:
0
'Error: 0'
1
2
3
總結(jié)
好的丑掺,讓我們來(lái)總結(jié)一下.then()
和.race()
吧,??
-
Promise.all()
的作用是接收一組異步任務(wù)述雾,然后并行執(zhí)行異步任務(wù)街州,并且在所有異步操作執(zhí)行完后才執(zhí)行回調(diào)。 -
.race()
的作用也是接收一組異步任務(wù)玻孟,然后并行執(zhí)行異步任務(wù)唆缴,只保留取第一個(gè)執(zhí)行完成的異步操作的結(jié)果,其他的方法仍在執(zhí)行取募,不過(guò)執(zhí)行結(jié)果會(huì)被拋棄琐谤。 -
Promise.all().then()
結(jié)果中數(shù)組的順序和Promise.all()
接收到的數(shù)組順序一致。
5. async/await的幾道題
既然談到了Promise
玩敏,那就肯定得再說(shuō)說(shuō)async/await
斗忌,在很多時(shí)候async
和Promise
的解法差不多,又有些不一樣旺聚。不信你來(lái)看看題目一织阳。
5.1 題目一
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
async1();
console.log('start')
這道基礎(chǔ)題輸出的是啥?
答案:
'async1 start''async2''start''async1 end'
過(guò)程分析:
- 首先一進(jìn)來(lái)是創(chuàng)建了兩個(gè)函數(shù)的砰粹,我們先不看函數(shù)的創(chuàng)建位置唧躲,而是看它的調(diào)用位置
- 發(fā)現(xiàn)
async1
函數(shù)被調(diào)用了,然后去看看調(diào)用的內(nèi)容 - 執(zhí)行函數(shù)中的同步代碼
async1 start
,之后碰到了await
弄痹,它會(huì)阻塞async1
后面代碼的執(zhí)行饭入,因此會(huì)先去執(zhí)行async2
中的同步代碼async2
,然后跳出async1
- 跳出
async1
函數(shù)后肛真,執(zhí)行同步代碼start
- 在一輪宏任務(wù)全部執(zhí)行完之后谐丢,再來(lái)執(zhí)行剛剛
await
后面的內(nèi)容async1 end
。
(在這里蚓让,你可以理解為await
后面的內(nèi)容就相當(dāng)于放到了Promise.then
的里面)
來(lái)看看區(qū)別乾忱,如果我們把await async2()
換成一個(gè)new Promise
呢?
async function async1() {
console.log("async1 start");
new Promise(resolve => {
console.log('promise')
})
console.log("async1 end");
}
async1();
console.log("start")
此時(shí)的執(zhí)行結(jié)果為:
'async start''promise''async1 end''start'
可以看到new Promise()
并不會(huì)阻塞后面的同步代碼async1 end
的執(zhí)行历极。
5.2 題目二
現(xiàn)在將async
結(jié)合定時(shí)器看看窄瘟。
給題目一中的 async2
函數(shù)中加上一個(gè)定時(shí)器:
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
setTimeout(() => {
console.log('timer')
}, 0)
console.log("async2");
}
async1();
console.log("start")
沒錯(cuò),定時(shí)器始終還是最后執(zhí)行的趟卸,它被放到下一條宏任務(wù)的延遲隊(duì)列中蹄葱。
答案:
'async1 start''async2''start''async1 end''timer'
5.3 題目三
來(lái)吧,小伙伴們衰腌,讓我們多加幾個(gè)定時(shí)器看看新蟆。??
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
setTimeout(() => {
console.log('timer1')
}, 0)
}
async function async2() {
setTimeout(() => {
console.log('timer2')
}, 0)
console.log("async2");
}
async1();
setTimeout(() => {
console.log('timer3')
}, 0)
console.log("start")
思考一下??,執(zhí)行結(jié)果會(huì)是什么右蕊?
其實(shí)如果你能做到這里了琼稻,說(shuō)明你前面的那些知識(shí)點(diǎn)也都掌握了,我就不需要太過(guò)詳細(xì)的步驟分析了饶囚。
直接公布答案吧:
'async1 start''async2''start''async1 end''timer2''timer3''timer1'
定時(shí)器誰(shuí)先執(zhí)行帕翻,你只需要關(guān)注誰(shuí)先被調(diào)用的以及延遲時(shí)間是多少,這道題中延遲時(shí)間都是0
萝风,所以只要關(guān)注誰(shuí)先被調(diào)用的嘀掸。。
5.4 題目四
正常情況下规惰,async
中的await
命令是一個(gè)Promise
對(duì)象睬塌,返回該對(duì)象的結(jié)果。
但如果不是Promise
對(duì)象的話歇万,就會(huì)直接返回對(duì)應(yīng)的值揩晴,相當(dāng)于Promise.resolve()
async function fn () {
// return await 1234
// 等同于
return 123
}
fn().then(res => console.log(res))
結(jié)果:
123
5.5 題目五
async function async1 () {
console.log('async1 start');
await new Promise(resolve => {
console.log('promise1')
})
console.log('async1 success');
return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')
這道題目比較有意思,大家要注意了贪磺。
在async1
中await
后面的Promise
是沒有返回值的硫兰,也就是它的狀態(tài)始終是pending
狀態(tài),因此相當(dāng)于一直在await
寒锚,await
劫映,await
卻始終沒有響應(yīng)...
所以在await
之后的內(nèi)容是不會(huì)執(zhí)行的违孝,也包括async1
后面的 .then
。
答案為:
'script start''async1 start''promise1''script end'
5.6 題目六
讓我們給5.5
中的Promise
加上resolve
:
async function async1 () {
console.log('async1 start');
await new Promise(resolve => {
console.log('promise1')
resolve('promise1 resolve')
}).then(res => console.log(res))
console.log('async1 success');
return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')
現(xiàn)在Promise
有了返回值了泳赋,因此await
后面的內(nèi)容將會(huì)被執(zhí)行:
'script start''async1 start''promise1''script end''promise1 resolve''async1 success''async1 end'
5.7 題目七
async function async1 () {
console.log('async1 start');
await new Promise(resolve => {
console.log('promise1')
resolve('promise resolve')
})
console.log('async1 success');
return 'async1 end'
}
console.log('srcipt start')
async1().then(res => {
console.log(res)
})
new Promise(resolve => {
console.log('promise2')
setTimeout(() => {
console.log('timer')
})
})
這道題應(yīng)該也不難雌桑,不過(guò)有一點(diǎn)需要注意的,在async1
中的new Promise
它的resovle
的值和async1().then()
里的值是沒有關(guān)系的摹蘑,很多小伙伴可能看到resovle('promise resolve')
就會(huì)誤以為是async1().then()
中的返回值筹燕。
因此這里的執(zhí)行結(jié)果為:
'script start''async1 start''promise1''promise2''async1 success''sync1 end''timer'
5.8 題目八
我們?cè)賮?lái)看一道頭條曾經(jīng)的面試題:
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(function() {
console.log("setTimeout");
}, 0);
async1();
new Promise(function(resolve) {
console.log("promise1");
resolve();
}).then(function() {
console.log("promise2");
});
console.log('script end')
有了上面??幾題做基礎(chǔ)轧飞,相信你很快也能答上來(lái)了衅鹿。
自信的寫下你們的答案吧。
'script start''async1 start''async2''promise1''script end''async1 end''promise2''setTimeout'
(這道題最后async1 end
和promise2
的順序其實(shí)在網(wǎng)上飽受爭(zhēng)議过咬,我這里使用瀏覽器Chrome V80
大渤,Node v12.16.1
的執(zhí)行結(jié)果都是上面這個(gè)答案)
5.9 題目九
好的??,async/await
大法已練成掸绞,咱們繼續(xù):
async function testSometing() {
console.log("執(zhí)行testSometing");
return "testSometing";
}
async function testAsync() {
console.log("執(zhí)行testAsync");
return Promise.resolve("hello async");
}
async function test() {
console.log("test start...");
const v1 = await testSometing();
console.log(v1);
const v2 = await testAsync();
console.log(v2);
console.log(v1, v2);
}
test();
var promise = new Promise(resolve => {
console.log("promise start...");
resolve("promise");
});
promise.then(val => console.log(val));
console.log("test end...");
答案:
'test start...'
'執(zhí)行testSometing'
'promise start...'
'test end...'
'testSometing'
'執(zhí)行testAsync'
'promise'
'hello async'
'testSometing' 'hello async'
6. async處理錯(cuò)誤
6.1 題目一
在async
中泵三,如果 await
后面的內(nèi)容是一個(gè)異常或者錯(cuò)誤的話衔掸,會(huì)怎樣呢烫幕?
async function async1 () {
await async2();
console.log('async1');
return 'async1 success'
}
async function async2 () {
return new Promise((resolve, reject) => {
console.log('async2')
reject('error')
})
}
async1().then(res => console.log(res))
例如這道題中,await
后面跟著的是一個(gè)狀態(tài)為rejected
的promise
敞映。
如果在async函數(shù)中拋出了錯(cuò)誤较曼,則終止錯(cuò)誤結(jié)果,不會(huì)繼續(xù)向下執(zhí)行振愿。
所以答案為:
'async2'Uncaught (in promise) error
如果改為throw new Error
也是一樣的:
async function async1 () {
console.log('async1');
throw new Error('error!!!')
return 'async1 success'}async1().then(res => console.log(res))
結(jié)果為:
'async1'Uncaught (in promise) Error: error!!!
6.2 題目二
如果想要使得錯(cuò)誤的地方不影響async
函數(shù)后續(xù)的執(zhí)行的話捷犹,可以使用try catch
async function async1 () {
try {
await Promise.reject('error!!!')
} catch(e) {
console.log(e)
}
console.log('async1');
return Promise.resolve('async1 success')
}
async1().then(res => console.log(res))
console.log('script start')
這里的結(jié)果為:
'script start''error!!!''async1''async1 success'
或者你可以直接在Promise.reject
后面跟著一個(gè)catch()
方法:
async function async1 () {
// try {
// await Promise.reject('error!!!')
// } catch(e) {
// console.log(e)
// }
await Promise.reject('error!!!')
.catch(e => console.log(e))
console.log('async1');
return Promise.resolve('async1 success')
}
async1().then(res => console.log(res))
console.log('script start')
運(yùn)行結(jié)果是一樣的。
7. 綜合題
上面??的題目都是被我拆分著說(shuō)一些功能點(diǎn)冕末,現(xiàn)在讓我們來(lái)做一些比較難的綜合題吧萍歉。
7.1 題目一
const first = () => (new Promise((resolve, reject) => {
console.log(3);
let p = new Promise((resolve, reject) => {
console.log(7);
setTimeout(() => {
console.log(5);
resolve(6);
console.log(p)
}, 0)
resolve(1);
});
resolve(2);
p.then((arg) => {
console.log(arg);
});
}));
first().then((arg) => {
console.log(arg);
});
console.log(4);
過(guò)程分析:
- 第一段代碼定義的是一個(gè)函數(shù),所以我們得看看它是在哪執(zhí)行的档桃,發(fā)現(xiàn)它在
4
之前枪孩,所以可以來(lái)看看first
函數(shù)里面的內(nèi)容了。(這一步有點(diǎn)類似于題目1.5
) - 函數(shù)
first
返回的是一個(gè)new Promise()
藻肄,因此先執(zhí)行里面的同步代碼3
- 接著又遇到了一個(gè)
new Promise()
蔑舞,直接執(zhí)行里面的同步代碼7
- 執(zhí)行完
7
之后,在p
中仅炊,遇到了一個(gè)定時(shí)器斗幼,先將它放到下一個(gè)宏任務(wù)隊(duì)列里不管它,接著向下走 - 碰到了
resolve(1)
抚垄,這里就把p
的狀態(tài)改為了resolved
蜕窿,且返回值為1
谋逻,不過(guò)這里也先不執(zhí)行 - 跳出
p
,碰到了resolve(2)
桐经,這里的resolve(2)
毁兆,表示的是把first
函數(shù)返回的那個(gè)Promise
的狀態(tài)改了,也先不管它阴挣。 - 然后碰到了
p.then
气堕,將它加入本次循環(huán)的微任務(wù)列表,等待執(zhí)行 - 跳出
first
函數(shù)畔咧,遇到了first().then()
茎芭,將它加入本次循環(huán)的微任務(wù)列表(p.then
的后面執(zhí)行) - 然后執(zhí)行同步代碼
4
- 本輪的同步代碼全部執(zhí)行完畢,查找微任務(wù)列表誓沸,發(fā)現(xiàn)
p.then
和first().then()
梅桩,依次執(zhí)行,打印出1和2
- 本輪任務(wù)執(zhí)行完畢了拜隧,發(fā)現(xiàn)還有一個(gè)定時(shí)器沒有跑完宿百,接著執(zhí)行這個(gè)定時(shí)器里的內(nèi)容,執(zhí)行同步代碼
5
- 然后又遇到了一個(gè)
resolve(6)
洪添,它是放在p
里的垦页,但是p
的狀態(tài)在之前已經(jīng)發(fā)生過(guò)改變了,因此這里就不會(huì)再改變干奢,也就是說(shuō)resolve(6)
相當(dāng)于沒任何用處痊焊,因此打印出來(lái)的p
為Promise{<resolved>: 1}
。(這一步類似于題目3.1
)
結(jié)果:
374125Promise{<resolved>: 1}
做對(duì)了的小伙伴獎(jiǎng)勵(lì)自己一朵小(大)
紅(嘴)
花(巴)
吧律胀,??
7.2 題目二
const async1 = async () => {
console.log('async1');
setTimeout(() => {
console.log('timer1')
}, 2000)
await new Promise(resolve => {
console.log('promise1')
})
console.log('async1 end')
return 'async1 success'
}
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.catch(4)
.then(res => console.log(res))
setTimeout(() => {
console.log('timer2')
}, 1000)
注意的知識(shí)點(diǎn):
* `async`函數(shù)中`await`的`new Promise`要是沒有返回值的話則不執(zhí)行后面的內(nèi)容(類似題`5.5`)
* `.then`函數(shù)中的參數(shù)期待的是函數(shù)宋光,如果不是函數(shù)的話會(huì)發(fā)生穿透(類似題`3.8` )
* 注意定時(shí)器的延遲時(shí)間
因此本題答案為:
'script start''async1''promise1''script end'1'timer2''timer1'
#### 7.3 題目三
const p1 = new Promise((resolve) => {
setTimeout(() => {
resolve('resolve3');
console.log('timer1')
}, 0)
resolve('resovle1');
resolve('resolve2');
}).then(res => {
console.log(res)
setTimeout(() => {
console.log(p1)
}, 1000)
}).finally(res => {
console.log('finally', res)
})
注意的知識(shí)點(diǎn):
* `Promise`的狀態(tài)一旦改變就無(wú)法改變(類似題目`3.5`)
* `finally`不管`Promise`的狀態(tài)是`resolved`還是`rejected`都會(huì)執(zhí)行,且它的回調(diào)函數(shù)是沒有參數(shù)的(類似`3.10`)
答案:
'resolve1''finally' undefined'timer1'Promise{<resolved>: undefined}
### 8\. 幾道大廠的面試題
#### 8.1 使用Promise實(shí)現(xiàn)每隔1秒輸出1,2,3
這道題比較簡(jiǎn)單的一種做法是可以用`Promise`配合著`reduce`不停的在`promise`后面疊加`.then`炭菌,請(qǐng)看下面的代碼:
const arr = [1, 2, 3]
arr.reduce((p, x) => {
return p.then(() => {
return new Promise(r => {
setTimeout(() => r(console.log(x)), 1000)
})
})
}, Promise.resolve())
或者你可以更簡(jiǎn)單一點(diǎn)寫:
const arr = [1, 2, 3]
arr.reduce((p, x) =>
p.then(() =>
new Promise(r =>
setTimeout(() => r(console.log(x)), 1000))),
Promise.resolve())
參考鏈接:如何讓異步操作順序執(zhí)行
#### 8.2 使用Promise實(shí)現(xiàn)紅綠燈交替重復(fù)亮
紅燈3秒亮一次罪佳,黃燈2秒亮一次,綠燈1秒亮一次黑低;如何讓三個(gè)燈不斷交替重復(fù)亮燈赘艳?(用Promise實(shí)現(xiàn))三個(gè)亮燈函數(shù)已經(jīng)存在:
function red() {
console.log('red');}function green() {
console.log('green');}function yellow() {
console.log('yellow');
}
答案:
const time = (timer) => {
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, timer)
})
}
const ajax1 = () => time(2000).then(() => {
console.log(1);
return 1
})
const ajax2 = () => time(1000).then(() => {
console.log(2);
return 2
})
const ajax3 = () => time(1000).then(() => {
console.log(3);
return 3
})
function mergePromise () {
// 在這里寫代碼
}
mergePromise([ajax1, ajax2, ajax3]).then(data => {
console.log("done");
console.log(data); // data 為 [1, 2, 3]
});
// 要求分別輸出
// 1
// 2
// 3
// done
// [1, 2, 3]
#### 8.3 實(shí)現(xiàn)mergePromise函數(shù)
實(shí)現(xiàn)mergePromise函數(shù),把傳進(jìn)去的數(shù)組按順序先后執(zhí)行克握,并且把返回的數(shù)據(jù)先后放到數(shù)組data中蕾管。
const time = (timer) => { return new Promise(resolve => { setTimeout(() => { resolve() }, timer) })}const ajax1 = () => time(2000).then(() => { console.log(1); return 1})const ajax2 = () => time(1000).then(() => { console.log(2); return 2})const ajax3 = () => time(1000).then(() => { console.log(3); return 3})function mergePromise () { // 在這里寫代碼}mergePromise([ajax1, ajax2, ajax3]).then(data => { console.log("done"); console.log(data); // data 為 [1, 2, 3]});// 要求分別輸出// 1// 2// 3// done// [1, 2, 3]
這道題有點(diǎn)類似于`Promise.all()`,不過(guò)`.all()`不需要管執(zhí)行順序菩暗,只需要并發(fā)執(zhí)行就行了掰曾。但是這里需要等上一個(gè)執(zhí)行完畢之后才能執(zhí)行下一個(gè)。
解題思路:
* 定義一個(gè)數(shù)組`data`用于保存所有異步操作的結(jié)果
* 初始化一個(gè)`const promise = Promise.resolve()`停团,然后循環(huán)遍歷數(shù)組旷坦,在`promise`后面添加執(zhí)行`ajax`任務(wù)掏熬,同時(shí)要將添加的結(jié)果重新賦值到`promise`上。
答案:
function mergePromise (ajaxArray) {
// 存放每個(gè)ajax的結(jié)果
const data = [];
let promise = Promise.resolve();
ajaxArray.forEach(ajax => {
// 第一次的then為了用來(lái)調(diào)用ajax
// 第二次的then是為了獲取ajax的結(jié)果
promise = promise.then(ajax).then(res => {
data.push(res);
return data; // 把每次的結(jié)果返回
})
})
// 最后得到的promise它的值就是data
return promise;
}
#### 8.4 根據(jù)promiseA+實(shí)現(xiàn)一個(gè)自己的promise
說(shuō)真的秒梅,這道題被問(wèn)到的概率還是挺高的旗芬,而且要說(shuō)的內(nèi)容也很多...
這里偷個(gè)懶,不想細(xì)說(shuō)了...
不過(guò)哈捆蜀,我保證疮丛,下下題我一定仔細(xì)說(shuō) ??.
![image](https://upload-images.jianshu.io/upload_images/21481816-720baa3632f60aea?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
來(lái)吧,給你們一些好的寶典:
* 《Promise不會(huì)辆它?誊薄?看這里!C渚暇屋!史上最通俗易懂的Promise!6蠢薄!》
* 《寫一個(gè)符合 Promises/A+ 規(guī)范并可配合 ES7 async/await 使用的 Promise》
#### 8.5 封裝一個(gè)異步加載圖片的方法
這個(gè)相對(duì)簡(jiǎn)單一些昙衅,只需要在圖片的`onload`函數(shù)中扬霜,使用`resolve`返回一下就可以了。
來(lái)看看具體代碼:
function loadImg(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = function() {
console.log("一張圖片加載完成");
resolve(img);
};
img.onerror = function() {
reject(new Error('Could not load image at' + url));
};
img.src = url;
});
#### 8.6 限制異步操作的并發(fā)個(gè)數(shù)并盡可能快的完成全部
有8個(gè)圖片資源的url而涉,已經(jīng)存儲(chǔ)在數(shù)組`urls`中著瓶。
`urls`類似于`['https://image1.png', 'https://image2.png', ....]`
而且已經(jīng)有一個(gè)函數(shù)`function loadImg`,輸入一個(gè)`url`鏈接啼县,返回一個(gè)`Promise`材原,該`Promise`在圖片下載完成的時(shí)候`resolve`,下載失敗則`reject`季眷。
但有一個(gè)要求余蟹,任何時(shí)刻同時(shí)下載的鏈接**數(shù)量不可以超過(guò)3個(gè)**。
請(qǐng)寫一段代碼實(shí)現(xiàn)這個(gè)需求子刮,要求**盡可能快速**地將所有圖片下載完成威酒。
var urls = [
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting1.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting2.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting3.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting4.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting5.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn6.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn7.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn8.png",
];
function loadImg(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = function() {
console.log("一張圖片加載完成");
resolve(img);
};
img.onerror = function() {
reject(new Error('Could not load image at' + url));
};
img.src = url;
});
看到這道題時(shí),我最開始的想法是:
* 拿到`urls`挺峡,然后將這個(gè)數(shù)組每3個(gè)`url`一組創(chuàng)建成一個(gè)二維數(shù)組
* 然后用`Promise.all()`每次加載一組`url`(也就是并發(fā)3個(gè))葵孤,這一組加載完再加載下一組。
這個(gè)想法從技術(shù)上說(shuō)并不難實(shí)現(xiàn)橱赠,有點(diǎn)類似于第三題弦疮。不過(guò)缺點(diǎn)也明顯恕刘,那就是每次都要等到上一組全部加載完之后,才加載下一組篮奄,那如果上一組有`2`個(gè)已經(jīng)加載完了,還有`1`個(gè)特別慢钙畔,還在加載,要等這個(gè)慢的也加載完才能進(jìn)入下一組。這明顯會(huì)照撑郏卡頓,影響加載效率纺裁。
但是開始沒有考慮這么多诫肠,因此有了第一個(gè)版本。
**如果你有興趣可以看看想法一的代碼欺缘,雖然對(duì)你沒什么幫助栋豫,想直接知道比較好的做法的小伙伴請(qǐng)?zhí)较敕ǘ?*
![image](https://upload-images.jianshu.io/upload_images/21481816-52df0c2aaae06396?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
**想法一**??:
function limitLoad (urls, handler, limit) {
const data = []; // 存儲(chǔ)所有的加載結(jié)果
let p = Promise.resolve();
const handleUrls = (urls) => { // 這個(gè)函數(shù)是為了生成3個(gè)url為一組的二維數(shù)組
const doubleDim = [];
const len = Math.ceil(urls.length / limit); // Math.ceil(8 / 3) = 3
console.log(len) // 3, 表示二維數(shù)組的長(zhǎng)度為3
for (let i = 0; i < len; i++) {
doubleDim.push(urls.slice(i * limit, (i + 1) * limit))
}
return doubleDim;
}
const ajaxImage = (urlCollect) => { // 將一組字符串url 轉(zhuǎn)換為一個(gè)加載圖片的數(shù)組
console.log(urlCollect)
return urlCollect.map(url => handler(url))
}
const doubleDim = handleUrls(urls); // 得到3個(gè)url為一組的二維數(shù)組
doubleDim.forEach(urlCollect => {
p = p.then(() => Promise.all(ajaxImage(urlCollect))).then(res => {
data.push(...res); // 將每次的結(jié)果展開,并存儲(chǔ)到data中 (res為:[img, img, img])
return data;
})
})
return p;
}
limitLoad(urls, loadImg, 3).then(res => {
console.log(res); // 最終得到的是長(zhǎng)度為8的img數(shù)組: [img, img, img, ...]
res.forEach(img => {
document.body.appendChild(img);
})
});
**想法二**??:
參考LHH大翰仔仔-Promise面試題
既然題目的要求是保證每次并發(fā)請(qǐng)求的數(shù)量為3谚殊,那么我們可以先請(qǐng)求`urls`中的前面三個(gè)(下標(biāo)為`0,1,2`)丧鸯,并且請(qǐng)求的時(shí)候使用`Promise.race()`來(lái)同時(shí)請(qǐng)求,三個(gè)中有一個(gè)先完成了(例如下標(biāo)為`1`的圖片)嫩絮,我們就把這個(gè)當(dāng)前數(shù)組中已經(jīng)完成的那一項(xiàng)(第`1`項(xiàng))換成還沒有請(qǐng)求的那一項(xiàng)(`urls`中下標(biāo)為`3`)丛肢。
直到`urls`已經(jīng)遍歷完了,然后將最后三個(gè)沒有完成的請(qǐng)求(也就是狀態(tài)沒有改變的`Promise`)用`Promise.all()`來(lái)加載它們剿干。
不多說(shuō)蜂怎,流程圖都給你畫好了,你可以結(jié)合流程圖再來(lái)看代碼置尔。
![image.gif](https://upload-images.jianshu.io/upload_images/21481816-0f892d29d095cf0d.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
為了方便你查看杠步,我截了個(gè)圖,不過(guò)代碼在后面也有
(說(shuō)真的榜轿,要我看這一大長(zhǎng)串代碼我也不愿意...)
![image](https://upload-images.jianshu.io/upload_images/21481816-0edceea1e8b08e5f?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
代碼:
```jacascript
function limitLoad(urls, handler, limit) {
let sequence = [].concat(urls); // 復(fù)制urls
// 這一步是為了初始化 promises 這個(gè)"容器"
let promises = sequence.splice(0, limit).map((url, index) => {
return handler(url).then(() => {
// 返回下標(biāo)是為了知道數(shù)組中是哪一項(xiàng)最先完成
return index;
});
});
// 注意這里要將整個(gè)變量過(guò)程返回幽歼,這樣得到的就是一個(gè)Promise,可以在外面鏈?zhǔn)秸{(diào)用
return sequence
.reduce((pCollect, url) => {
return pCollect
.then(() => {
return Promise.race(promises); // 返回已經(jīng)完成的下標(biāo)
})
.then(fastestIndex => { // 獲取到已經(jīng)完成的下標(biāo)
// 將"容器"內(nèi)已經(jīng)完成的那一項(xiàng)替換
promises[fastestIndex] = handler(url).then(
() => {
return fastestIndex; // 要繼續(xù)將這個(gè)下標(biāo)返回谬盐,以便下一次變量
}
);
})
.catch(err => {
console.error(err);
});
}, Promise.resolve()) // 初始化傳入
.then(() => { // 最后三個(gè)用.all來(lái)調(diào)用
return Promise.all(promises);
});
}
limitLoad(urls, loadImg, 3)
.then(res => {
console.log("圖片全部加載完畢");
console.log(res);
})
.catch(err => {
console.error(err);
});
后語(yǔ)
知識(shí)無(wú)價(jià)甸私,支持原創(chuàng)。
參考文章:
- 《ES6之Promise常見面試題》
- 《如何讓異步操作順序執(zhí)行》
- 《大白話講解Promise(一)
- 《LHH大翰仔仔-Promise面試題》
- 《今日頭條async/await面試題執(zhí)行順序》
- 《(2.4w字,建議收藏)??原生JS靈魂之問(wèn)(下), 沖刺??進(jìn)階最后一公里(附個(gè)人成長(zhǎng)經(jīng)驗(yàn)分享)》
你盼世界设褐, 我盼望你無(wú)bug颠蕴。這篇文章就介紹到這里,一口氣刷完了45
道題助析,真的很爽有沒有...
反正我做到后面是越來(lái)越有勁犀被,也越來(lái)越自信了(有點(diǎn)飄,收一下...)