你盼世界,我盼望你無bug
。Hello 你們好!我是霖呆呆!javascript
(是否是封面和標題取的好就能把你騙進來 😁)html
時隔一週不見,霖呆呆我終於更新文章了,小聲嘀咕說想我了...前端
呸...java
咳咳,其實我一直在隱忍準備來一發大的好不。es6
這不,這一章節就是整理了45
道Promise
的筆試題讓你們爽一爽 😁。面試
其實想要寫一篇關於Promise
的文章是由於以前在寫別的文章的時候被評論區的一名讀者無情的嘲諷了👎:ajax
"做者的Promise必定很爛"
segmentfault
因此編寫這麼一個主題霖呆呆我不是爲了證實什麼,而是想說:數組
"說我爛我能夠學啊"
promise
另外查了不少關於Promise
的面試題,有些一上來就很難的,有些連着幾篇題目都是同樣的,還有一些比較好的文章介紹的都是一些硬知識點。
這篇文章是一篇比較純的Promise
筆試文章,是我本身在作題的時候,根據題目想要的考點來反敲知識點,而後再由這個知識點編寫從淺到深的的題目。
因此你能夠看到題目中有一些基礎題,而後再從基礎題慢慢的變難,若是你看着感受這段位配不上你的話,請答應我堅持看下去,會愈來愈難的...
咳咳,按部就班嘛...
本文的題目沒有到特別深刻,不過應該覆蓋了大部分的考點,另外爲了避免把你們繞混,答案也沒有考慮在Node
的執行結果,執行結果全爲瀏覽器環境下。所以若是你都會作的話,能夠盡情的在評論區再給我一個👎
,放心,我脾氣很好的...
OK👌, 來看看經過閱讀本篇文章你能夠學到:
在作下面👇的題目以前,我但願你能清楚幾個知識點。
(若是你感受一上來不想看這些列舉的知識點的話,直接看後面的例子再來理解它們也能夠)
event loop
它的執行順序:
Web Worker
任務,有則執行微任務包括:MutationObserver
、Promise.then()或catch()
、Promise爲基礎開發的其它技術,好比fetch API
、V8
的垃圾回收過程、Node獨有的process.nextTick
。
宏任務包括:script
、setTimeout
、setInterval
、setImmediate
、I/O
、UI rendering
。
注意⚠️:在全部任務開始的時候,因爲宏任務中包括了script
,因此瀏覽器會先執行一個宏任務,在這個過程當中你看到的延遲任務(例如setTimeout
)將被放到下一輪宏任務中來執行。
const promise1 = new Promise((resolve, reject) => { console.log('promise1') }) console.log('1', promise1); 複製代碼
過程分析:
new Promise
,執行該構造函數中的代碼promise1
1
,此時promise1
沒有被resolve
或者reject
,所以狀態仍是pending
結果:
'promise1' '1' Promise{<pending>} 複製代碼
const promise = new Promise((resolve, reject) => { console.log(1); resolve('success') console.log(2); }); promise.then(() => { console.log(3); }); console.log(4); 複製代碼
過程分析:
new Promise
,執行其中的同步代碼1
resolve('success')
, 將promise
的狀態改成了resolved
而且將值保存下來2
promise
,往下執行,碰到promise.then
這個微任務,將其加入微任務隊列4
promise.then
這個微任務且狀態爲resolved
,執行它。結果:
1 2 4 3
複製代碼
const promise = new Promise((resolve, reject) => { console.log(1); console.log(2); }); promise.then(() => { console.log(3); }); console.log(4); 複製代碼
過程分析
promise
中並無resolve
或者reject
promise.then
並不會執行,它只有在被改變了狀態以後纔會執行。結果:
1 2 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); 複製代碼
過程分析:
new Promise
,執行該構造函數中的代碼promise1
resolve
函數, 將promise1
的狀態改變爲resolved
, 並將結果保存下來promise1.then
這個微任務,將它放入微任務隊列promise2
是一個新的狀態爲pending
的Promise
1
, 同時打印出promise1
的狀態是resolved
2
,同時打印出promise2
的狀態是pending
promise1.then
這個微任務且狀態爲resolved
,執行它。結果:
'promise1' '1' Promise{<resolved>: 'resolve1'} '2' Promise{<pending>} 'resolve1' 複製代碼
接下來看看這道題:
const fn = () => (new Promise((resolve, reject) => { console.log(1); resolve('success') })) fn().then(res => { console.log(res) }) console.log('start') 複製代碼
這道題裏最早執行的是'start'
嗎 🤔️ ?
請仔細看看哦,fn
函數它是直接返回了一個new Promise
的,並且fn
函數的調用是在start
以前,因此它裏面的內容應該會先執行。
結果:
1 'start' 'success' 複製代碼
若是把fn
的調用放到start
以後呢?
const fn = () => new Promise((resolve, reject) => { console.log(1); resolve("success"); }); console.log("start"); fn().then(res => { console.log(res); }); 複製代碼
是的,如今start
就在1
以前打印出來了,由於fn
函數是以後執行的。
注意⚠️:以前咱們很容易就覺得看到new Promise()就執行它的第一個參數函數了,其實這是不對的,就像這兩道題中,咱們得注意它是否是被包裹在函數當中,若是是的話,只有在函數調用的時候纔會執行。
答案:
"start" 1 "success" 複製代碼
好嘞,學完了這幾道基礎題,讓咱們來用個表情包壓壓驚。
console.log('start') setTimeout(() => { console.log('time') }) Promise.resolve().then(() => { console.log('resolve') }) console.log('end') 複製代碼
過程分析:
start
和end
。setTimout
做爲一個宏任務被放入宏任務隊列(下一個)Promise.then
做爲一個微任務被放入微任務隊列Promise.then
,執行它setTimeout
,執行。結果:
'start' 'end' 'resolve' 'time' 複製代碼
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); 複製代碼
過程分析:
和題目1.2
很像,不過在resolve
的外層加了一層setTimeout
定時器。
new Promise
,執行該構造函數中的代碼1
2
promise
函數,遇到promise.then
,但其狀態仍是爲pending
,這裏理解爲先不執行4
setTimeout
定時器,執行它timerStart
,而後遇到了resolve
,將promise
的狀態改成resolved
且保存結果並將以前的promise.then
推入微任務隊列timerEnd
promise.then
這個微任務,執行它。所以執行結果爲:
1 2 4 "timerStart" "timerEnd" "success" 複製代碼
題目三分了兩個題目,由於看着都差很少,不過執行的結果卻不同,你們不妨先猜猜下面兩個題目分別執行什麼:
(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') 複製代碼
執行結果:
'start' 'timer1' 'timer2' 'timer3' 複製代碼
'start' 'timer1' 'promise' 'timer2' 複製代碼
這兩個例子,看着好像只是把第一個定時器中的內容換了一下而已。
一個是爲定時器timer3
,一個是爲Promise.then
可是若是是定時器timer3
的話,它會在timer2
後執行,而Promise.then
倒是在timer2
以前執行。
你能夠這樣理解,Promise.then
是微任務,它會被加入到本輪中的微任務列表,而定時器timer3
是宏任務,它會被加入到下一輪的宏任務中。
理解完這兩個案例,能夠來看看下面一道比較難的題目了。
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
中執行定時器,又在定時器中執行promise
;
而且要注意的是,這裏的Promise
是直接resolve
的,而以前的new Promise
不同。
(偷偷告訴你,這道題往下一點有流程圖)
所以過程分析爲:
Promise.resolve().then
這個微任務,將then
中的內容加入第一次的微任務隊列標記爲微1timer1
,將它加入下一次宏任務的延遲列表,標記爲宏2,等待執行(先無論裏面是什麼內容)start
promise.then
這個微任務須要執行promise1
,而後發現定時器timer2
,將它加入宏2的後面,標記爲宏3timer1
promise2
這個微任務,將它加入這次循環的微任務隊列,標記爲微2promise2
,執行它timer2
因此結果爲:
'start' 'promise1' 'timer1' 'promise2' 'timer2' 複製代碼
若是感受有點繞的話,能夠看下面這張圖,就一目瞭然了。
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) 複製代碼
過程分析:
new Promise
中的函數,碰到setTimeout
將它加入下一個宏任務列表new Promise
,碰到promise1.then
這個微任務,但其狀態仍是爲pending
,這裏理解爲先不執行promise2
是一個新的狀態爲pending
的Promise
console.log('promise1')
,且打印出的promise1
的狀態爲pending
console.log('promise2')
,且打印出的promise2
的狀態爲pending
promise1
的狀態改成resolved
且保存結果並將以前的promise1.then
推入微任務隊列promise1.then
,它拋出了一個錯誤,且將promise2
的狀態設置爲了rejected
'promise1'
,且此時promise1
的狀態爲resolved
'promise2'
,且此時promise2
的狀態爲rejected
完整的結果爲:
'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!!!} 複製代碼
若是你上面這道題搞懂了以後,咱們就能夠來作作這道了,你應該能很快就給出答案:
const promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve("success"); console.log("timer1"); }, 1000); console.log("promise1裏的內容"); }); 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); 複製代碼
結果:
'promise1裏的內容' '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!!!} 複製代碼
額,可能你看到下面👇這麼多的1,2,3
脾氣就上來了,不是說好了本篇文章沒什麼屁話嘛,怎麼仍是這麼多一二三四。
😂,你要理解個人用心良苦啊,我這是幫你把知識點都列舉出來,作個總結而已。固然,你也能夠先不看,先去作後面的題,而後再回過頭來看這些,你就以爲這些點都好好懂啊,甚至都不須要記。
總結:
Promise
的狀態一經改變就不能再改變。(見3.1).then
和.catch
都會返回一個新的Promise
。(上面的👆1.4證實了)catch
無論被鏈接到哪裏,都能捕獲上層未捕捉過的錯誤。(見3.2)Promise
中,返回任意一個非 promise
的值都會被包裹成 promise
對象,例如return 2
會被包裝爲return Promise.resolve(2)
。Promise
的 .then
或者 .catch
能夠被調用屢次, 但若是Promise
內部的狀態一經改變,而且有了一個值,那麼後續每次調用.then
或者.catch
的時候都會直接拿到該值。(見3.5).then
或者 .catch
中 return
一個 error
對象並不會拋出錯誤,因此不會被後續的 .catch
捕獲。(見3.6).then
或 .catch
返回的值不能是 promise 自己,不然會形成死循環。(見3.7).then
或者 .catch
的參數指望是函數,傳入非函數則會發生值透傳。(見3.8).then
方法是能接收兩個參數的,第一個是處理成功的函數,第二個是處理失敗的函數,再某些時候你能夠認爲catch
是.then
第二個參數的簡便寫法。(見3.9).finally
方法也是返回一個Promise
,他在Promise
結束的時候,不管結果爲resolved
仍是rejected
,都會執行裏面的回調函數。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" 複製代碼
構造函數中的 resolve
或 reject
只有第一次執行有效,屢次調用沒有任何做用 。驗證了第一個結論,Promise
的狀態一經改變就不能再改變。
const promise = new Promise((resolve, reject) => { reject("error"); resolve("success2"); }); promise .then(res => { console.log("then1: ", res); }).then(res => { console.log("then2: ", res); }).catch(err => { console.log("catch: ", err); }).then(res => { console.log("then3: ", res); }) 複製代碼
結果:
"catch: " "error" "then3: " undefined 複製代碼
驗證了第三個結論,catch
無論被鏈接到哪裏,都能捕獲上層未捕捉過的錯誤。
至於then3
也會被執行,那是由於catch()
也會返回一個Promise
,且因爲這個Promise
沒有返回值,因此打印出來的是undefined
。
Promise.resolve(1) .then(res => { console.log(res); return 2; }) .catch(err => { return 3; }) .then(res => { console.log(res); }); 複製代碼
結果:
1
2
複製代碼
Promise
能夠鏈式調用,不過promise
每次調用 .then
或者 .catch
都會返回一個新的 promise
,從而實現了鏈式調用, 它並不像通常咱們任務的鏈式調用同樣return this
。
上面的輸出結果之因此依次打印出1
和2
,那是由於resolve(1)
以後走的是第一個then
方法,並無走catch
裏,因此第二個then
中的res
獲得的其實是第一個then
的返回值。
且return 2
會被包裝成resolve(2)
。
若是把3.3
中的Promise.resolve(1)
改成Promise.reject(1)
又會怎麼樣呢?
Promise.reject(1) .then(res => { console.log(res); return 2; }) .catch(err => { console.log(err); return 3 }) .then(res => { console.log(res); }); 複製代碼
結果:
1
3
複製代碼
結果打印的固然是 1 和 3
啦,由於reject(1)
此時走的就是catch
,且第二個then
中的res
獲得的就是catch
中的返回值。
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) }) 複製代碼
執行結果:
'timer' 'success' 1001 'success' 1002 複製代碼
固然,若是你足夠快的話,也可能兩個都是1001
。
Promise
的 .then
或者 .catch
能夠被調用屢次,但這裏 Promise
構造函數只執行一次。或者說 promise
內部狀態一經改變,而且有了一個值,那麼後續每次調用 .then
或者 .catch
都會直接拿到該值。
Promise.resolve().then(() => { return new Error('error!!!') }).then(res => { console.log("then: ", res) }).catch(err => { console.log("catch: ", err) }) 複製代碼
猜猜這裏的結果輸出的是什麼 🤔️ ?
你可能想到的是進入.catch
而後被捕獲了錯誤。
結果並非這樣的,它走的是.then
裏面:
"then: " "Error: error!!!" 複製代碼
這也驗證了第4點和第6點,返回任意一個非 promise
的值都會被包裹成 promise
對象,所以這裏的return new Error('error!!!')
也被包裹成了return Promise.resolve(new Error('error!!!'))
。
固然若是你拋出一個錯誤的話,能夠用下面👇兩的任意一種:
return Promise.reject(new Error('error!!!')); // or throw new Error('error!!!') 複製代碼
const promise = Promise.resolve().then(() => { return promise; }) promise.catch(console.err) 複製代碼
.then
或 .catch
返回的值不能是 promise 自己,不然會形成死循環。
所以結果會報錯:
Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise> 複製代碼
Promise.resolve(1) .then(2) .then(Promise.resolve(3)) .then(console.log) 複製代碼
這道題看着好像很簡單,又感受很複雜的樣子,怎麼這麼多個.then
啊... 😅
其實你只要記住原則8:.then
或者 .catch
的參數指望是函數,傳入非函數則會發生值透傳。
第一個then
和第二個then
中傳入的都不是函數,一個是數字類型,一個是對象類型,所以發生了透傳,將resolve(1)
的值直接傳到最後一個then
裏。
因此輸出結果爲:
1
複製代碼
下面來介紹一下.then
函數中的兩個參數。
第一個參數是用來處理Promise
成功的函數,第二個則是處理失敗的函數。
也就是說Promise.resolve('1')
的值會進入成功的函數,Promise.reject('2')
的值會進入失敗的函數。
讓咱們來看看這個例子🌰:
Promise.reject('err!!!') .then((res) => { console.log('success', res) }, (err) => { console.log('error', err) }).catch(err => { console.log('catch', err) }) 複製代碼
這裏的執行結果是:
'error' 'error!!!' 複製代碼
它進入的是then()
中的第二個參數裏面,而若是把第二個參數去掉,就進入了catch()
中:
Promise.reject('error!!!') .then((res) => { console.log('success', res) }).catch(err => { console.log('catch', err) }) 複製代碼
執行結果:
'catch' 'error!!!' 複製代碼
可是有一個問題,若是是這個案例呢?
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
調用的是resolve()
,所以.then()
執行的應該是success()
函數,但是success()
函數拋出的是一個錯誤,它會被後面的catch()
給捕獲到,而不是被fail1
函數捕獲。
所以執行結果爲:
fail2 Error: error!!!
at success
複製代碼
接着來看看.finally()
,這個功能通常不太用在面試中,不過若是碰到了你也應該知道該如何處理。
其實你只要記住它三個很重要的知識點就能夠了:
.finally()
方法無論Promise
對象最後的狀態如何都會執行.finally()
方法的回調函數不接受任何的參數,也就是說你在.finally()
函數中是無法知道Promise
最終的狀態是resolved
仍是rejected
的Promise
對象。來看看這個簡單的例子🌰:
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函數', res) }) 複製代碼
這兩個Promise
的.finally
都會執行,且就算finally2
返回了新的值,它後面的then()
函數接收到的結果卻仍是'2'
,所以打印結果爲:
'1' 'finally2' 'finally' 'finally2後面的then函數' '2' 複製代碼
至於爲何finally2
的打印要在finally
前面,請看下一個例子中的解析。
不過在此以前讓咱們再來確認一下,finally
中要是拋出的是一個異常是怎樣的:
Promise.resolve('1') .finally(() => { console.log('finally1') throw new Error('我是finally中拋出的異常') }) .then(res => { console.log('finally後面的then函數', res) }) .catch(err => { console.log('捕獲錯誤', err) }) 複製代碼
執行結果爲:
'finally1' '捕獲錯誤' Error: 我是finally中拋出的異常 複製代碼
可是若是改成return new Error('我是finally中拋出的異常')
,打印出來的就是'finally後面的then函數 1'
OK,👌,讓咱們來看一個比較難的例子🌰:
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')) 複製代碼
執行過程:
promise1
和promise2
,先無論接着往下看。promise1
函數先被調用了,而後執行裏面new Promise
的同步代碼打印出promise1
resolve(1)
,將p
的狀態改成了resolved
並將結果保存下來。promise1
內的函數內容已經執行完了,跳出該函數promise1().then()
,因爲promise1
的狀態已經發生了改變且爲resolved
所以將promise1().then()
這條微任務加入本輪的微任務列表(這是第一個微任務).finally
加入微任務列表,那是由於.then
自己就是一個微任務,它鏈式後面的內容必須得等當前這個微任務執行完纔會執行,所以這裏咱們先無論.finally()
promise2()
函數,其中返回的new Promise
中並無同步代碼須要執行,因此執行reject('error')
的時候將promise2
函數中的Promise
的狀態變爲了rejected
promise2
函數,遇到了promise2().catch()
,將其加入當前的微任務隊列(這是第二個微任務),且鏈式調用後面的內容得等該任務執行完後才執行,和.then()
同樣。promise1().then()
,執行它,打印出1
,而後遇到了.finally()
這個微任務將它加入微任務列表(這是第三個微任務)等待執行promise2().catch()
打印出error
,執行完後將finally2
加入微任務加入微任務列表(這是第四個微任務)finally1
和finally2
。結果:
'promise1' '1' 'error' 'finally1' 'finally2' 複製代碼
在這道題中其實能拓展的東西挺多的,以前沒有提到,那就是你能夠理解爲鏈式調用後面的內容須要等前一個調用執行完纔會執行。
就像是這裏的finally()
會等promise1().then()
執行完纔會將finally()
加入微任務隊列,其實若是這道題中你把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')) 複製代碼
在作下面👇的題目以前,讓咱們先來了解一下Promise.all()
和Promise.race()
的用法。
通俗來講,.all()
的做用是接收一組異步任務,而後並行執行異步任務,而且在全部異步操做執行完後才執行回調。
.race()
的做用也是接收一組異步任務,而後並行執行異步任務,只保留取第一個執行完成的異步操做的結果,其餘的方法仍在執行,不過執行結果會被拋棄。
來看看題目一。
咱們知道若是直接在腳本文件中定義一個Promise
,它構造函數的第一個參數是會當即執行的,就像這樣:
const p1 = new Promise(r => console.log('當即打印')) 複製代碼
控制檯中會當即打印出 「當即打印」。
所以爲了控制它何時執行,咱們能夠用一個函數包裹着它,在須要它執行的時候,調用這個函數就能夠了:
function runP1 () { const p1 = new Promise(r => console.log('當即打印')) return p1 } runP1() // 調用此函數時才執行 複製代碼
OK 👌, 讓咱們迴歸正題。
如今來構建這麼一個函數:
function runAsync (x) { const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000)) return p } 複製代碼
該函數傳入一個值x
,而後間隔一秒後打印出這個x
。
若是我用.all()
來執行它會怎樣呢?
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)) 複製代碼
先來想一想此段代碼在瀏覽器中會如何執行?
沒錯,當你打開頁面的時候,在間隔一秒後,控制檯會同時打印出1, 2, 3
,還有一個數組[1, 2, 3]
。
1
2
3
[1, 2, 3]
複製代碼
因此你如今能理解這句話的意思了嗎:有了all,你就能夠並行執行多個異步操做,而且在一個回調中處理全部的返回數據。
.all()
後面的.then()
裏的回調函數接收的就是全部異步操做的結果。
並且這個結果中數組的順序和Promise.all()
接收到的數組順序一致!!!
有一個場景是很適合用這個的,一些遊戲類的素材比較多的應用,打開網頁時,預先加載須要用到的各類資源如圖片、flash以及各類靜態文件。全部的都加載完後,咱們再進行頁面的初始化。
我新增了一個runReject
函數,它用來在1000 * x
秒後reject
一個錯誤。
同時.catch()
函數可以捕獲到.all()
裏最早的那個異常,而且只執行一次。
想一想這道題會怎樣執行呢 🤔️?
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)) 複製代碼
不賣關子了 😁,讓我來公佈答案:
// 1s後輸出
1
3
// 2s後輸出
2
Error: 2
// 4s後輸出
4
複製代碼
沒錯,就像我以前說的,.catch
是會捕獲最早的那個異常,在這道題目中最早的異常就是runReject(2)
的結果。
另外,若是一組異步操做中有一個異常都不會進入.then()
的第一個回調函數參數中。
注意,爲何不說是不進入.then()
中呢 🤔️?
哈哈,你們別忘了.then()
方法的第二個參數也是能夠捕獲錯誤的:
Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)]) .then(res => console.log(res), err => console.log(err)) 複製代碼
接下來讓咱們看看另外一個有趣的方法.race
。
讓我看看大家的英語水平如何?
快!一秒鐘告訴我race
是什麼意思?
好吧...大家果真很強...
race
,比賽,賽跑的意思。
因此使用.race()
方法,它只會獲取最早執行完成的那個結果,其它的異步任務雖然也會繼續進行下去,不過race
已經無論那些任務的結果了。
來,改造一下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)) 複製代碼
執行結果爲:
1 'result: ' 1 2 3 複製代碼
這個race有什麼用呢?使用場景仍是不少的,好比咱們能夠用race給某個異步請求設置超時時間,而且在超時後執行相應的操做
改造一下題目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)); 複製代碼
遇到錯誤的話,也是同樣的,在這道題中,runReject(0)
最早執行完,因此進入了catch()
中:
0 'Error: 0' 1 2 3 複製代碼
好的,讓咱們來總結一下.then()
和.race()
吧,😄
Promise.all()
的做用是接收一組異步任務,而後並行執行異步任務,而且在全部異步操做執行完後才執行回調。.race()
的做用也是接收一組異步任務,而後並行執行異步任務,只保留取第一個執行完成的異步操做的結果,其餘的方法仍在執行,不過執行結果會被拋棄。Promise.all().then()
結果中數組的順序和Promise.all()
接收到的數組順序一致。all和race
傳入的數組中若是有會拋出異常的異步任務,那麼只有最早拋出的錯誤會被捕獲,而且是被then
的第二個參數或者後面的catch
捕獲;但並不會影響數組中其它的異步任務的執行。既然談到了Promise
,那就確定得再說說async/await
,在不少時候async
和Promise
的解法差很少,又有些不同。不信你來看看題目一。
async function async1() { console.log("async1 start"); await async2(); console.log("async1 end"); } async function async2() { console.log("async2"); } async1(); console.log('start') 複製代碼
這道基礎題輸出的是啥?
答案:
'async1 start' 'async2' 'start' 'async1 end' 複製代碼
過程分析:
async1
函數被調用了,而後去看看調用的內容async1 start
,以後碰到了await
,它會阻塞async1
後面代碼的執行,所以會先去執行async2
中的同步代碼async2
,而後跳出async1
async1
函數後,執行同步代碼start
await
後面的內容async1 end
。在這裏,你能夠理解爲「緊跟着await後面的語句至關於放到了new Promise中,下一行及以後的語句至關於放在Promise.then中」。
讓咱們來看看將await
轉換爲Promise.then
的僞代碼:
async function async1() { console.log("async1 start"); // 原來代碼 // await async2(); // console.log("async1 end"); // 轉換後代碼 new Promise(resolve => { console.log("async2") resolve() }).then(res => console.log("async1 end")) } async function async2() { console.log("async2"); } async1(); console.log("start") 複製代碼
轉換後的僞代碼和前面的執行結果是同樣的。(感謝評論區Wing93和Jexxie小夥伴的指出)
另外關於await
和Promise
的區別,若是咱們把await async2()
換成一個new Promise
呢?
async function async1() { console.log("async1 start"); new Promise(resolve => { console.log('promise') }) console.log("async1 end"); } async1(); console.log("start") 複製代碼
此時的執行結果爲:
'async start' 'promise' 'async1 end' 'start' 複製代碼
能夠看到new Promise()
並不會阻塞後面的同步代碼async1 end
的執行。
如今將async
結合定時器看看。
給題目一中的 async2
函數中加上一個定時器:
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") 複製代碼
沒錯,定時器始終仍是最後執行的,它被放到下一條宏任務的延遲隊列中。
答案:
'async1 start' 'async2' 'start' 'async1 end' 'timer' 複製代碼
來吧,小夥伴們,讓咱們多加幾個定時器看看。😁
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") 複製代碼
思考一下🤔,執行結果會是什麼?
其實若是你能作到這裏了,說明你前面的那些知識點也都掌握了,我就不須要太過詳細的步驟分析了。
直接公佈答案吧:
'async1 start' 'async2' 'start' 'async1 end' 'timer2' 'timer3' 'timer1' 複製代碼
定時器誰先執行,你只須要關注誰先被調用的以及延遲時間是多少,這道題中延遲時間都是0
,因此只要關注誰先被調用的。。
正常狀況下,async
中的await
命令是一個Promise
對象,返回該對象的結果。
但若是不是Promise
對象的話,就會直接返回對應的值,至關於Promise.resolve()
async function fn () { // return await 1234 // 等同於 return 123 } fn().then(res => console.log(res)) 複製代碼
結果:
123
複製代碼
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
是沒有返回值的,也就是它的狀態始終是pending
狀態,所以至關於一直在await
,await
,await
卻始終沒有響應...
因此在await
以後的內容是不會執行的,也包括async1
後面的 .then
。
答案爲:
'script start' 'async1 start' 'promise1' 'script end' 複製代碼
讓咱們給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') 複製代碼
如今Promise
有了返回值了,所以await
後面的內容將會被執行:
'script start' 'async1 start' 'promise1' 'script end' 'promise1 resolve' 'async1 success' 'async1 end' 複製代碼
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') }) }) 複製代碼
這道題應該也不難,不過有一點須要注意的,在async1
中的new Promise
它的resovle
的值和async1().then()
裏的值是沒有關係的,不少小夥伴可能看到resovle('promise resolve')
就會誤覺得是async1().then()
中的返回值。
所以這裏的執行結果爲:
'script start' 'async1 start' 'promise1' 'promise2' 'async1 success' 'async1 end' 'timer' 複製代碼
咱們再來看一道頭條曾經的面試題:
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') 複製代碼
有了上面👆幾題作基礎,相信你很快也能答上來了。
自信的寫下大家的答案吧。
'script start' 'async1 start' 'async2' 'promise1' 'script end' 'async1 end' 'promise2' 'setTimeout' 複製代碼
(這道題最後async1 end
和promise2
的順序其實在網上飽受爭議,我這裏使用瀏覽器Chrome V80
,Node v12.16.1
的執行結果都是上面這個答案)
好的👌,async/await
大法已練成,我們繼續:
async function testSometing() { console.log("執行testSometing"); return "testSometing"; } async function testAsync() { console.log("執行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...' '執行testSometing' 'promise start...' 'test end...' 'testSometing' '執行testAsync' 'promise' 'hello async' 'testSometing' 'hello async' 複製代碼
在async
中,若是 await
後面的內容是一個異常或者錯誤的話,會怎樣呢?
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
後面跟着的是一個狀態爲rejected
的promise
。
若是在async函數中拋出了錯誤,則終止錯誤結果,不會繼續向下執行。
因此答案爲:
'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)) 複製代碼
結果爲:
'async1' Uncaught (in promise) Error: error!!! 複製代碼
若是想要使得錯誤的地方不影響async
函數後續的執行的話,可使用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') 複製代碼
這裏的結果爲:
'script start' 'error!!!' 'async1' 'async1 success' 複製代碼
或者你能夠直接在Promise.reject
後面跟着一個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') 複製代碼
運行結果是同樣的。
上面👆的題目都是被我拆分着說一些功能點,如今讓咱們來作一些比較難的綜合題吧。
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); 複製代碼
過程分析:
4
以前,因此能夠來看看first
函數裏面的內容了。(這一步有點相似於題目1.5
)first
返回的是一個new Promise()
,所以先執行裏面的同步代碼3
new Promise()
,直接執行裏面的同步代碼7
7
以後,在p
中,遇到了一個定時器,先將它放到下一個宏任務隊列裏無論它,接着向下走resolve(1)
,這裏就把p
的狀態改成了resolved
,且返回值爲1
,不過這裏也先不執行p
,碰到了resolve(2)
,這裏的resolve(2)
,表示的是把first
函數返回的那個Promise
的狀態改了,也先無論它。p.then
,將它加入本次循環的微任務列表,等待執行first
函數,遇到了first().then()
,將它加入本次循環的微任務列表(p.then
的後面執行)4
p.then
和first().then()
,依次執行,打印出1和2
5
resolve(6)
,它是放在p
裏的,可是p
的狀態在以前已經發生過改變了,所以這裏就不會再改變,也就是說resolve(6)
至關於沒任何用處,所以打印出來的p
爲Promise{<resolved>: 1}
。(這一步相似於題目3.1
)結果:
3
7
4
1
2
5
Promise{<resolved>: 1}
複製代碼
作對了的小夥伴獎勵本身一朵小(大)
紅(嘴)
花(巴)
吧,😄
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) 複製代碼
注意的知識點:
async
函數中await
的new Promise
要是沒有返回值的話則不執行後面的內容(相似題5.5
).then
函數中的參數期待的是函數,若是不是函數的話會發生透傳(相似題3.8
)所以本題答案爲:
'script start' 'async1' 'promise1' 'script end' 1 'timer2' 'timer1' 複製代碼
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) }) 複製代碼
注意的知識點:
Promise
的狀態一旦改變就沒法改變(相似題目3.5
)finally
無論Promise
的狀態是resolved
仍是rejected
都會執行,且它的回調函數是接收不到Promise
的結果的,因此finally()
中的res
是一個迷惑項(相似3.10
)。p1
實際上是.finally
的返回值,咱們知道.finally
的返回值若是在沒有拋出錯誤的狀況下默認會是上一個Promise
的返回值(3.10
中也有提到), 而這道題中.finally
上一個Promise
是.then()
,可是這個.then()
並無返回值,因此p1
打印出來的Promise
的值會是undefined
,若是你在定時器的下面加上一個return 1
,則值就會變成1
(感謝掘友JS叢中過的指出)。答案:
'resolve1' 'finally' undefined 'timer1' Promise{<resolved>: undefined} 複製代碼
這道題比較簡單的一種作法是能夠用Promise
配合着reduce
不停的在promise
後面疊加.then
,請看下面的代碼:
const arr = [1, 2, 3] arr.reduce((p, x) => { return p.then(() => { return new Promise(r => { setTimeout(() => r(console.log(x)), 1000) }) }) }, Promise.resolve()) 複製代碼
或者你能夠更簡單一點寫:
const arr = [1, 2, 3] arr.reduce((p, x) => p.then(() => new Promise(r => setTimeout(() => r(console.log(x)), 1000))), Promise.resolve()) 複製代碼
參考連接:如何讓異步操做順序執行
拓展題
這道拓展題來自於「萬物皆可愛的LINGLONG 」小姐姐,炒雞棒 😁👍。
題目是這樣的,她把我上面👆寫的箭頭函數版本改造了一下:
const arr = [1, 2, 3]; const result = arr.reduce((p, x) => p.then(new Promise(r => setTimeout(() => r(console.log(x)), 1000))), Promise.resolve()); 複製代碼
眼尖的小夥伴看出區別了嗎?😁
p.then
裏的代碼由() => new Promise(...)
變成了new Promise(...)
。
如今執行結果就大不相同了。
在一秒後按順序同時打印出一、二、3
:
1
2
3
複製代碼
咦 🤔️?爲何會這樣呢 🤔️?
只是一個小小的改變卻有大大的區別。
其實剛開始看到的時候霖呆呆我也愣了那麼幾秒😂。不過等咱們一步一步拆分並對想不通的地方寫了幾個案例來看就理解了。
評論區和小姐姐扯了一大堆,結果把她越弄越糊😂。後來我改變了一種思路來描述,以爲應該直接上僞代碼:
const arr = [1, 2, 3] arr.reduce((p, x) => p.then(() => new Promise(r => setTimeout(() => r(console.log(x)), 1000))), Promise.resolve()) 複製代碼
轉換爲僞代碼就是這樣:
(至關因而用reduce
不停的日後面疊加.then
)
Promise.resolve() .then(() => { return new Promise(r => { setTimeout(() => { r(console.log(1)) }, 1000) }) }) .then(r => { return new Promise(r => { setTimeout(() => { r(console.log(2)) }, 1000) }) }) .then(r => { return new Promise(r => { setTimeout(() => { r(console.log(3)) }, 1000) }) }) 複製代碼
能夠看到,每個.then
都是依賴於上一個new Promise
什麼時候被resolve
了纔會執行的,例如第二個.then()
,它要等r(console.log(1)
這段代碼執行了,纔會執行。
那麼r(console.log(1))
何時執行呢?就是在第一個定時器(也就是一秒後)觸發的時候才執行。這樣就保證了後面接着的.then()
要等前一個定時器執行完才能執行,也就是隔一秒輸出。
而若是是這樣寫的話:
const arr = [1, 2, 3]; const result = arr.reduce((p, x) => p.then(new Promise(r => setTimeout(() => r(console.log(x)), 1000))), Promise.resolve()); 複製代碼
它的僞代碼就是這樣:
(每一個then
裏面的第一個參數不是一個函數)
Promise.resolve() .then(new Promise(r => { setTimeout(() => { r(console.log(1)) }, 1000) })) .then(new Promise(r => { setTimeout(() => { r(console.log(2)) }, 1000) })) .then(new Promise(r => { setTimeout(() => { r(console.log(3)) }, 1000) })) 複製代碼
p.then()
裏面的參數若是不是函數的話,會發生透傳,這個在3.8
中已經提過了。可是發生透傳,.then()
裏的代碼就不執行了嗎?
並非的,咱們來看這個例子:
const p = Promise.resolve(1).then(console.log('我不關心結果')) console.log(p) p.then((res) => console.log(res)) 複製代碼
很明顯這裏也發生了透傳,可是'我不關心結果'
也仍是被打印出來了,而且因爲透傳,p.then()
裏獲取到的res
就是1
,所以會打印出:
'我不關心結果' Promise{ [[PromiseStatus]]: "resolved" [[PromiseValue]]: 1 } 1 複製代碼
(第二行打印出Promise{<pending>}
的小夥伴請把這個對象展開來看)
這個例子代表,就算髮生了透傳,p.then()
中的代碼依舊也是會執行的。
因此回到
.then(new Promise(r => { setTimeout(() => { r(console.log(1)) }, 1000) })) 複製代碼
中,如今.then()
中就至關因而執行一段同步代碼:
new Promise(r => { setTimeout(() => { r(console.log(1)) }, 1000) }) 複製代碼
而這段代碼的做用是向延遲隊列中push
一個一秒後執行的定時器任務。
而且在push
完定時器以後,代碼就立刻進入了下一個.then
(由於既然第一個.then
已是透傳的了就沒有必要等它的執行結果了)
下一個.then
居然也是一個透傳,OK,那我繼續push
這個定時器,而後再執行第三個.then
。
三個.then
已經執行完成了,如今咱們的延遲隊列中已經有了三個定時器等待執行,而且三個定時器的延遲時間都是1000ms!!!。
因此等到了時間以後,就會同時打印出來了一、二、3
。(其實準確來講,不是同時打印的,不過中間相差的時間很是很是短,大可忽略它)
如今你是否理解了其中的區別呢 😝。
紅燈3秒亮一次,黃燈2秒亮一次,綠燈1秒亮一次;如何讓三個燈不斷交替重複亮燈?(用Promise實現)三個亮燈函數已經存在:
function red() { console.log('red'); } function green() { console.log('green'); } function yellow() { console.log('yellow'); } 複製代碼
答案:
function red() { console.log("red"); } function green() { console.log("green"); } function yellow() { console.log("yellow"); } const light = function (timer, cb) { return new Promise(resolve => { setTimeout(() => { cb() resolve() }, timer) }) } const step = function () { Promise.resolve().then(() => { return light(3000, red) }).then(() => { return light(2000, green) }).then(() => { return light(1000, yellow) }).then(() => { return step() }) } step(); 複製代碼
實現mergePromise函數,把傳進去的數組按順序前後執行,而且把返回的數據前後放到數組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] 複製代碼
這道題有點相似於Promise.all()
,不過.all()
不須要管執行順序,只須要併發執行就好了。可是這裏須要等上一個執行完畢以後才能執行下一個。
解題思路:
data
用於保存全部異步操做的結果const promise = Promise.resolve()
,而後循環遍歷數組,在promise
後面添加執行ajax
任務,同時要將添加的結果從新賦值到promise
上。答案:
function mergePromise (ajaxArray) { // 存放每一個ajax的結果 const data = []; let promise = Promise.resolve(); ajaxArray.forEach(ajax => { // 第一次的then爲了用來調用ajax // 第二次的then是爲了獲取ajax的結果 promise = promise.then(ajax).then(res => { data.push(res); return data; // 把每次的結果返回 }) }) // 最後獲得的promise它的值就是data return promise; } 複製代碼
說真的,這道題被問到的機率仍是挺高的,並且要說的內容也不少...
霖呆呆這裏偷個懶,不想細說了...
不過哈,我保證,下下題我必定仔細說 😼.
來吧,給大家一些好的寶典:
這個相對簡單一些,只須要在圖片的onload
函數中,使用resolve
返回一下就能夠了。
來看看具體代碼:
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個圖片資源的url,已經存儲在數組urls
中。
urls
相似於['https://image1.png', 'https://image2.png', ....]
並且已經有一個函數function loadImg
,輸入一個url
連接,返回一個Promise
,該Promise
在圖片下載完成的時候resolve
,下載失敗則reject
。
但有一個要求,任什麼時候刻同時下載的連接數量不能夠超過3個。
請寫一段代碼實現這個需求,要求儘量快速地將全部圖片下載完成。
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; }); 複製代碼
看到這道題時,我最開始的想法是:
urls
,而後將這個數組每3個url
一組建立成一個二維數組Promise.all()
每次加載一組url
(也就是併發3個),這一組加載完再加載下一組。這個想法從技術上說並不難實現,有點相似於第三題。不過缺點也明顯,那就是每次都要等到上一組所有加載完以後,才加載下一組,那若是上一組有2
個已經加載完了,還有1
個特別慢,還在加載,要等這個慢的也加載完才能進入下一組。這明顯會照常卡頓,影響加載效率。
可是開始沒有考慮這麼多,所以有了第一個版本。
若是你有興趣能夠看看想法一的代碼,雖然對你沒什麼幫助,想直接知道比較好的作法的小夥伴請跳到想法二
想法一💡:
function limitLoad (urls, handler, limit) { const data = []; // 存儲全部的加載結果 let p = Promise.resolve(); const handleUrls = (urls) => { // 這個函數是爲了生成3個url爲一組的二維數組 const doubleDim = []; const len = Math.ceil(urls.length / limit); // Math.ceil(8 / 3) = 3 console.log(len) // 3, 表示二維數組的長度爲3 for (let i = 0; i < len; i++) { doubleDim.push(urls.slice(i * limit, (i + 1) * limit)) } return doubleDim; } const ajaxImage = (urlCollect) => { // 將一組字符串url 轉換爲一個加載圖片的數組 console.log(urlCollect) return urlCollect.map(url => handler(url)) } const doubleDim = handleUrls(urls); // 獲得3個url爲一組的二維數組 doubleDim.forEach(urlCollect => { p = p.then(() => Promise.all(ajaxImage(urlCollect))).then(res => { data.push(...res); // 將每次的結果展開,並存儲到data中 (res爲:[img, img, img]) return data; }) }) return p; } limitLoad(urls, loadImg, 3).then(res => { console.log(res); // 最終獲得的是長度爲8的img數組: [img, img, img, ...] res.forEach(img => { document.body.appendChild(img); }) }); 複製代碼
想法二💡:
既然題目的要求是保證每次併發請求的數量爲3,那麼咱們能夠先請求urls
中的前面三個(下標爲0,1,2
),而且請求的時候使用Promise.race()
來同時請求,三個中有一個先完成了(例以下標爲1
的圖片),咱們就把這個當前數組中已經完成的那一項(第1
項)換成尚未請求的那一項(urls
中下標爲3
)。
直到urls
已經遍歷完了,而後將最後三個沒有完成的請求(也就是狀態沒有改變的Promise
)用Promise.all()
來加載它們。
很少說,流程圖都給你畫好了,你能夠結合流程圖再來看代碼。
爲了方便你查看,我截了個圖,不過代碼在後面也有
(說真的,要我看這一大長串代碼我也不肯意...)
代碼:
function limitLoad(urls, handler, limit) { let sequence = [].concat(urls); // 複製urls // 這一步是爲了初始化 promises 這個"容器" let promises = sequence.splice(0, limit).map((url, index) => { return handler(url).then(() => { // 返回下標是爲了知道數組中是哪一項最早完成 return index; }); }); // 注意這裏要將整個變量過程返回,這樣獲得的就是一個Promise,能夠在外面鏈式調用 return sequence .reduce((pCollect, url) => { return pCollect .then(() => { return Promise.race(promises); // 返回已經完成的下標 }) .then(fastestIndex => { // 獲取到已經完成的下標 // 將"容器"內已經完成的那一項替換 promises[fastestIndex] = handler(url).then( () => { return fastestIndex; // 要繼續將這個下標返回,以便下一次變量 } ); }) .catch(err => { console.error(err); }); }, Promise.resolve()) // 初始化傳入 .then(() => { // 最後三個用.all來調用 return Promise.all(promises); }); } limitLoad(urls, loadImg, 3) .then(res => { console.log("圖片所有加載完畢"); console.log(res); }) .catch(err => { console.error(err); }); 複製代碼
知識無價,支持原創。
參考文章:
你盼世界, 我盼望你無bug。這篇文章就介紹到這裏,一口氣刷完了45
道題,真的很爽有沒有...
反正我作到後面是愈來愈有勁,也愈來愈自信了(有點飄,收一下...)
喜歡霖呆呆的小夥還但願能夠關注霖呆呆的公衆號 LinDaiDai
或者掃一掃下面的二維碼👇👇👇.
我會不定時的更新一些前端方面的知識內容以及本身的原創文章🎉
你的鼓勵就是我持續創做的主要動力 😊.
相關推薦: