上期講了promise
基本概念和用法,今天結合上期的內容,講解幾道經典的相關面試題。javascript
Promise
構造函數會當即執行,而Promise.then()
內部的代碼在當次事件循環的結尾當即執行(微任務)。promise
的狀態一旦由等待pending
變爲成功fulfilled
或者失敗rejected
。那麼當前promise
被標記爲完成,後面則不會再次改變該狀態。resolve
函數和reject
函數都將當前Promise
狀態改成完成,並將異步結果,或者錯誤結果當作參數返回。Promise.resolve(value)
返回一個狀態由給定 value 決定的 Promise 對象。若是該值是 thenable(即,帶有 then 方法的對象),返回的 Promise 對象的最終狀態由 then 方法執行決定;不然的話(該 value 爲空,基本類型或者不帶 then 方法的對象),返回的 Promise 對象狀態爲 fulfilled,而且將該 value 傳遞給對應的 then 方法。一般而言,若是你不知道一個值是不是 Promise 對象,使用 Promise.resolve(value) 來返回一個 Promise 對象,這樣就能將該 value 以 Promise 對象形式使用。html
Promise.all(iterable)/Promise.race(iterable)
簡單理解,這2個函數,是將接收到的promise
列表的結果返回,區別是,all
是等待全部的promise
都觸發成功了,纔會返回,而arce
有一個成功了就會返回結果。其中任何一個promise
執行失敗了,都會直接返回失敗的結果。前端
promise
對象的構造函數只會調用一次,then
方法和catch
方法都能屢次調用,但一旦有了肯定的結果,再次調用就會直接返回結果。const promise = new Promise((resolve, reject) => {
console.log(1); resolve(); console.log(2); reject('error'); }) promise.then(() => { console.log(3); }).catch(e => console.log(e)) console.log(4); 複製代碼
能夠看:規則一,promise
構造函數的代碼會當即執行,then
或者reject
裏面的代碼會放入異步微任務隊列,在宏任務結束後會當即執行。規則二:promise
的狀態一旦變動爲成功或者失敗,則不會再次改變,因此執行結果爲:1,2,4,3。而catch
裏面的函數不會再執行。html5
const promise = new Promise((resolve, reject) => {
setTimeout(() => { console.log('once') resolve('success') }, 1000) }) promise.then((res) => { console.log(res) }) promise.then((res) => { console.log(res) }) 複製代碼
根據規則6,promise
的構造函數只會執行一次,而then
方法能夠屢次調用,可是第二次是直接返回結果,不會有異步等待的時間,因此執行結果是: 過一秒打印:once,success,success
。java
在瀏覽器上,下面的程序會一次輸出哪些內容?node
const p1 = () => (new Promise((resolve, reject) => {
console.log(1); let p2 = new Promise((resolve, reject) => { console.log(2); const timeOut1 = setTimeout(() => { console.log(3); resolve(4); }, 0) resolve(5); }); resolve(6); p2.then((arg) => { console.log(arg); }); })); const timeOut2 = setTimeout(() => { console.log(8); const p3 = new Promise(reject => { reject(9); }).then(res => { console.log(res) }) }, 0) p1().then((arg) => { console.log(arg); }); console.log(10); 複製代碼
事件循環:javascript
的執行規則裏面有個事件循環Event Loot的規則,在事件循環中,異步事件會放到異步隊列裏面,可是異步隊列裏面又分爲宏任務和微任務,瀏覽器端的宏任務通常有:script標籤,setTimeout,setInterval,setImmediate,requestAnimationFrame
。微任務有:MutationObserver,Promise.then catch finally
。宏任務會阻塞瀏覽器的渲染進程,微任務會在宏任務結束後當即執行,在渲染以前。web
回到題目,結果爲:'1,2,10,5,6,8,9,3'。你答對了嗎?若是對了,那你基本理解了事件隊列,微任務,宏任務了。面試
第一步:執行宏任務,結合規則一,輸出:1,2,10。這時候事件循環裏面有異步任務timeOut1,timeOut2,p2.then,p1.then
。chrome
第二步:宏任務執行完後Event Loop
會去任務隊列取異步任務,微任務會優先執行,這時候會前後執行p2.then,p1.then
,打印5,6。promise
第三步:微任務執行完了,開始宏任務,因爲2個settimeout
等待時間同樣,因此會執行先進入異步隊列的timeOut2,前後打印:8。執行宏任務的過程當中,p3.then微任務進入了隊列,宏任務執行完畢會執行微任務,輸出:9。以後執行timeOut1,輸出:3。
第四步:結合規則6,因爲p2這個Promise
對象的執行結果已經肯定,因此4不會被打印。
注:在node.js
上輸出結果並非這樣的,由於node.js
的事件循環跟瀏覽器端的有區別。
async/await
的狀況下,順序執行一組異步代碼函數,並輸出最後的結果。在上篇文章中,已經講到過,利用promise.resolve
結合reduce
能順序執行一組異步函數。
const applyAsync = (acc,val) => acc.then(val);
const composeAsync = (...dd) => x => dd.reduce(applyAsync, Promise.resolve(x)); const transformData = composeAsync(funca, funcb, funcc, funcd); transformData(1).then(result => console.log(result,'last result')).catch(e => console.log(e)); 複製代碼
以上代碼能夠封裝成工具來使用,利用的是規則4,promise.resolve
函數的特色,其中dd
能夠是一組同步函數,也能夠是異步函數。最後的結果在result
裏面,異常信息能在最後捕獲。想看更具體的能夠查看這篇文章: promise講解
順序加載10張圖片,圖片地址已知,可是同時最多加載3張圖片,要求用promise
實現。
const baseUrl = 'http://img.aizhifou.cn/';
const urls = ['1.png', '2.png', '3.png', '4.png', '5.png','6.png', '7.png', '8.png', '9.png', '10.png']; const loadImg = function (url, i) { return new Promise((resolve, reject) => { try { // 加載一張圖片 let image = new Image(); image.onload = function () { resolve(i) } image.onerror = function () { reject(i) }; image.src = baseUrl + url; } catch (e) { reject(i) } }) } function startLoadImage(urls, limits, endHandle) { // 當前存在的promise隊列 let promiseMap = {}; // 當前索引對應的加載狀態,不管成功,失敗都會標記爲true,格式: {0: true, 1: true, 2: true...} let loadIndexMap = {}; // 當前以及加載到的索引,方便找到下一個未加載的索引,爲了節省性能,其實能夠不要 let loadIndex = 0; const loadAImage = function () { // 全部的資源都進入了異步隊列 if (Object.keys(loadIndexMap).length === urls.length) { // 全部的資源都加載完畢,或者進入加載狀態,遞歸結束 const promiseList = Object.keys(promiseMap).reduce((arr, item) => {arr.push(promiseMap[item]); return arr}, []) Promise.all(promiseList).then(res => { // 這裏若是沒有加載失敗,就會在全部加載完畢後執行,若是其中某個錯誤了,這裏的結果就不許確,不過這個不是題目要求的。 console.log('all'); endHandle && endHandle() }).catch((e) => { console.log('end:' + e); }) } else { // 遍歷,知道里面有3個promise while (Object.keys(promiseMap).length < limits) { for (let i = loadIndex; i < urls.length; i++) { if (loadIndexMap[i] === undefined) { loadIndexMap[i] = false; promiseMap[i] = loadImg(urls[i], i); loadIndex = i; break; } } } // 獲取當前正在進行的promise列表,利用reduce從promiseMap裏面獲取 const promiseList = Object.keys(promiseMap).reduce((arr, item) => {arr.push(promiseMap[item]); return arr}, []) Promise.race(promiseList).then((index) => { // 其中一張加載成功,刪除當前promise,讓PromiseList小於limit,開始遞歸,加載下一張 console.log('end:' + index); loadIndexMap[index] = true; delete promiseMap[index]; loadAImage(); }).catch(e => { // 加載失敗也繼續 console.log('end:' + e); loadIndexMap[e] = true; delete promiseMap[e]; loadAImage(); }) } } loadAImage() } startLoadImage(urls, 3) 複製代碼
將代碼複製到chrome瀏覽器能夠看到下面的運行結果: 能夠看到,全部圖片加載完成,在沒有失敗的狀況下,打印出來all
。
解析:根據規則5,Promise.race
方法接受的參數中有一個promise
對象返回結果了就會當即觸發成功或者失敗的函數。這裏利用這個特性,先將promise
隊列循環加入,直到達到限制,等待race
,race
後又加入一個promise
,利用遞歸一直循環這個過程,到最後用promise.all
捕獲剩下的圖片加載。
寫出下面函數的執行結果:
Promise.resolve(1)
.then(2) .then(Promise.resolve(3)) .then(console.log) 複製代碼
根據規則4,Promise.resolve(1)
會返回一個promise對象
而且會將1當作then
的參數。而.then 或者 .catch 的參數指望是函數,傳入非函數則會發生值穿透。因此最後會輸出:1。
如何取消一個promise
? 剛開始拿到這個題會以爲比較蒙,實際上,咱們能夠用Promise,race
的特色,多個Promise
有個狀態變爲完成,就會立馬返回。
function wrap(p) {
let obj = {}; let p1 = new Promise((resolve, reject) => { obj.resolve = resolve; obj.reject = reject; }); obj.promise = Promise.race([p1, p]); return obj; } let promise = new Promise((resolve, reject) => { setTimeout(() => { resolve(123); }, 1000); }); let obj = wrap(promise); obj.promise.then(res => { console.log(res); }); // obj.resolve("請求被攔截了"); 複製代碼
一旦開發者在1秒內主動調用obj.resolve
,那麼obj.promise
方法就會被替換成咱們本身的方法,而不會執行let promise
的then
方法,實現上比較巧妙。
promise
對象在JavaScript
中的使用相對複雜,由於寫法多變,並且靈活,提供的方法又比較複雜難懂,在ES6普及的今天,使用範圍也廣,因此會高頻的出如今面試過程當中。
相關閱讀:
知道html5 Web Worker標準嗎?能實現JavaScript的多線程?
學習如逆水行舟,不進則退,前端技術飛速發展,若是天天不堅持學習,就會跟不上,我會陪着你們,天天堅持推送博文,跟你們一同進步,但願你們能關注我,第一時間收到最新文章。
我的公衆號: