Promise是ES6中的特性,如今不少前端框架像AngularJS,Vue等在HTTP請求以後都是返回的Promise處理,所以Promise是必需要掌握的一個知識點。前端
本文將爲你們分享15道由易到難的ES6 Promise題,幫助你快速理解Promise。ajax
const promise = new Promise((resolve, reject) => {
console.log(1)
resolve()
console.log(2)
})
promise.then(() => {
console.log(3)
})
console.log(4)
複製代碼
解析:編程
Promise 構造函數是同步執行的,promise.then 中的函數是異步執行的。數組
運行結果:promise
// => 1
// => 2
// => 4
// => 3
複製代碼
const first = () => (new Promise((resolve, reject) => {
console.log(3);
let p = new Promise((resolve, reject) => {
console.log(7);
setTimeout(() => {
console.log(5);
resolve(6);
}, 0)
resolve(1);
});
resolve(2);
p.then((arg) => {
console.log(arg);
});
}));
first().then((arg) => {
console.log(arg);
});
console.log(4);
複製代碼
解析:前端框架
這道題主要理解JS執行機制。markdown
第一輪事件循環,先執行宏任務,主script,new Promise 當即執行,輸出3,執行p這個new Promise操做,輸出7,發現setTimeout,將回調函數放入下一輪任務隊列(Event Quene),p的then,暫且命名爲then1,放入微任務隊列,且first也有then,命名爲then2,放入微任務隊列,執行console.log(4),輸出4,宏任務執行結束。併發
再執行微任務,執行then1,輸出1,執行then2,輸出3.框架
第一輪事件循環結束,開始執行第二輪。第二輪事件循環先執行宏任務裏面的,也就是setTimeout的回調,輸出5.resolve(6)不會生效,由於p的Promise狀態一旦改變就不會在變化了。異步
運行結果:
// => 3
// => 7
// => 4
// => 1
// => 2
// => 5
複製代碼
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)
複製代碼
解析:
promise有3種狀態:pending、fulfiled 或 rejected。狀態改變只能是 pending->fulfilled 或者 pending->rejected,狀態一旦改變則不能再變。上面 promise2 並非 promise1,而是返回的一個新的 Promise 實例。
運行結果:
promise1 Promise {<pending>}
promise2 Promise {<pending>}
Uncaught (in promise) Error: error!!!
at <anonymous>
promise1 Promise {<resolved>: "success"}
promise2 Promise {<rejected>: Error: error!!!
at <anonymous>}
複製代碼
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)
})
複製代碼
解析:
構造函數中的 resolve 或 reject 只有第一次執行有效,屢次調用沒有任何做用,呼應代碼而結論:Promise狀態一旦改變則不能再變。
運行結果:
then: success1
複製代碼
Promise.resolve(1)
.then((res) => {
console.log(res)
return 2
})
.catch((err) => {
return 3
})
.then((res) => {
console.log(res)
})
複製代碼
解析:
promise 能夠鏈式調用。提起鏈式調用咱們一般會想到經過 return this 實現,不過 Promise 並非這樣實現的,promise 每次調用 .then 或者 .catch 都會返回一個新的 Promise,從而實現了鏈式調用。
運行結果:
1
2
複製代碼
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('once')
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)
})
複製代碼
解析:
promise的 .then 或者 .catch 能夠被調用屢次,但這裏 Promise 構造函數只執行一次。或者說 promise 內部狀態一經改變,而且有了一個值,那麼後續每次調用 .then 或者 .catch 都會直接拿到該值。
運行結果:
once
success 1005
success 1007
複製代碼
Promise.resolve()
.then(() => {
return new Error('error!!!')
})
.then((res) => {
console.log('then: ', res)
})
.catch((err) => {
console.log('catch: ', err)
})
複製代碼
解析:
.then 或者 .catch 中 return 一個 error 對象並不會拋出錯誤,因此不會被後續的 .catch 捕獲,須要改爲其中的一種:
return Promise.reject(new Error('error!!!'))
throw new Error('error!!!')
由於返回任意一個非 Promise 的值都會被包裹成 promise 對象,即 return new Error('error!!!')等價於 return Promise.resolve(new Error('error!!!'))。
運行結果:
then: Error: error!!!
at <anonymous>
複製代碼
const promise = Promise.resolve()
.then(() => {
return promise
})
promise.catch(console.error)
複製代碼
解析:
.then 或 .catch 返回的值不能是 promise 自己,不然會形成死循環。相似於:
process.nextTick(function tick () {
console.log('tick')
process.nextTick(tick)
})
複製代碼
運行結果:
TypeError: Chaining cycle detected for promise #<Promise>
複製代碼
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
複製代碼
解析:
.then 或者 .catch 的參數指望是函數,傳入非函數則會發生值穿透。
運行結果:
1
複製代碼
Promise.resolve()
.then(function success (res) {
throw new Error('error')
}, function fail1 (e) {
console.error('fail1: ', e)
})
.catch(function fail2 (e) {
console.error('fail2: ', e)
})
複製代碼
解析:
.then 能夠接收兩個參數,第一個是處理成功的函數,第二個是處理錯誤的函數。
.catch 是 .then 第二個參數的簡便寫法,可是它們用法上有一點須要注意: .then 的第二個處理錯誤的函數捕獲不了第一個處理成功的函數拋出的錯誤,然後續的 .catch 能夠捕獲以前的錯誤。
固然如下代碼也能夠:
Promise.resolve()
.then(function success1 (res) {
throw new Error('error')
}, function fail1 (e) {
console.error('fail1: ', e)
})
.then(function success2 (res) {
}, function fail2 (e) {
console.error('fail2: ', e)
})
複製代碼
運行結果:
fail2: Error: error
at success (<anonymous>)
複製代碼
process.nextTick(() => {
console.log('nextTick')
})
Promise.resolve()
.then(() => {
console.log('then')
})
setImmediate(() => {
console.log('setImmediate')
})
console.log('end')
複製代碼
解析:
process.nextTick 和 promise.then 都屬於 microtask,而 setImmediate 屬於 macrotask,在事件循環的 check 階段執行。事件循環的每一個階段(macrotask) 之間都會執行 microtask ,事件循環的開始會執行一次 microtask。
運行結果:
end
nextTick
then
setImmediate
複製代碼
紅燈3秒亮一次,綠燈1秒亮一次,黃燈2秒亮一次:如何使用Promise讓三個燈不斷交替重複亮燈?(海康威視筆試題)
function red(){
console.log('red');
}
function green(){
console.log('green');
}
function yellow(){
console.log('yellow');
}
複製代碼
分析:
先看題目,題目要求紅燈亮事後,綠燈才能亮,綠燈亮事後,黃燈才能亮,黃燈亮事後,紅燈才能亮.....因此怎麼經過Promise實現?
換句話說,就是紅燈亮起時,承諾2s秒後亮綠燈,綠燈亮起時承諾1s後亮起黃燈,黃燈亮起時,承諾3s後亮紅燈......這顯然是一個Promise鏈式調用,看到這裏你內心或許就有思路了,咱們須要將咱們的每個亮燈動做寫在 then() 方法中,同時返回一個新的Promise,並將其狀態由pending設置爲fulfilled,容許下一盞燈亮起。
function red() {
console.log('red');
}
function green() {
console.log('green');
}
function yellow() {
console.log('yellow');
}
let myLight = (timer, cb) => {
return new Promise((resolve) => {
setTimeout(() => {
cb();
resolve();
}, timer);
});
};
let myStep = () => {
Promise.resolve().then(() => {
return myLight(3000, red);
}).then(() => {
return myLight(2000, green);
}).then(()=>{
return myLight(1000, yellow);
}).then(()=>{
myStep();
})
};
myStep();
複製代碼
// output:
// => red
// => green
// => yellow
// => red
// => green
// => yellow
// => red
複製代碼
請實現一個mergePromise函數,把傳進去的數組按順序前後執行,而且把返回的數據前後放到數組data中。
const timeout = ms => new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, ms);
});
const ajax1 = () => timeout(2000).then(() => {
console.log('1');
return 1;
});
const ajax2 = () => timeout(1000).then(() => {
console.log('2');
return 2;
});
const ajax3 = () => timeout(2000).then(() => {
console.log('3');
return 3;
});
const mergePromise = ajaxArray => {
// 在這裏實現你的代碼
};
mergePromise([ajax1, ajax2, ajax3]).then(data => {
console.log('done');
console.log(data); // data 爲 [1, 2, 3]
});
複製代碼
// 要求分別輸出
// 1
// 2
// 3
// done
// [1, 2, 3]
複製代碼
分析:
這道題主要考察用Promise控制異步流程,首先ajax1,ajax2,ajax3都是函數,只是這些函數執行後會返回一個Promise,按照題目要求只要順序執行這三個函數就行了,而後把結果放到data中;
答案:
const mergePromise = ajaxArray => {
// 在這裏實現你的代碼
// 保存數組中的函數執行後的結果
var data = [];
// Promise.resolve方法調用時不帶參數,直接返回一個resolved狀態的 Promise 對象。
var sequence = Promise.resolve();
ajaxArray.forEach(item => {
// 第一次的 then 方法用來執行數組中的每一個函數,
// 第二次的 then 方法接受數組中的函數執行後返回的結果,
// 並把結果添加到 data 中,而後把 data 返回。
sequence = sequence.then(item).then(res => {
data.push(res);
return data;
});
});
// 遍歷結束後,返回一個 Promise,也就是 sequence, 他的 [[PromiseValue]] 值就是 data,
// 而 data(保存數組中的函數執行後的結果) 也會做爲參數,傳入下次調用的 then 方法中。
return sequence;
};
複製代碼
現有8個圖片資源的url,已經存儲在數組urls中,且已有一個函數function loading,輸入一個url連接,返回一個Promise,該Promise在圖片下載完成的時候resolve,下載失敗則reject。
要求: 任什麼時候刻同時下載的連接數量不能夠超過3個。
請寫一段代碼實現這個需求,要求儘量快速地將全部圖片下載完成。
var urls = ['https://www.kkkk1000.com/images/getImgData/getImgDatadata.jpg', 'https://www.kkkk1000.com/images/getImgData/gray.gif', 'https://www.kkkk1000.com/images/getImgData/Particle.gif', 'https://www.kkkk1000.com/images/getImgData/arithmetic.png', 'https://www.kkkk1000.com/images/getImgData/arithmetic2.gif', 'https://www.kkkk1000.com/images/getImgData/getImgDataError.jpg', 'https://www.kkkk1000.com/images/getImgData/arithmetic.gif', 'https://www.kkkk1000.com/images/wxQrCode2.png'];
function loadImg(url) {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = () => {
console.log('一張圖片加載完成');
resolve();
}
img.onerror = reject;
img.src = url;
})
};
複製代碼
解析:
題目的意思是須要先併發請求3張圖片,當一張圖片加載完成後,又會繼續發起一張圖片的請求,讓併發數保持在3個,直到須要加載的圖片都所有發起請求。
用Promise來實現就是,先併發請求3個圖片資源,這樣獲得3個Promise,組成一個數組promises,而後不斷調用Promise.race來返回最快改變狀態的Promise,而後從數組promise中刪掉這個Promise對象,再加入一個新的Promise,直到所有的url被取完,最後使用Promise.all來處理一遍數組promise中沒有改變狀態的Promise。
function limitLoad(urls, handler, limit) {
// 對數組作一個拷貝
const sequence = […urls];
let promises = [];
複製代碼
//併發請求到最大數
promises = sequence.splice(0, limit).map((url, index) => {
// 這裏返回的 index 是任務在 promises 的腳標,用於在 Promise.race 以後找到完成的任務腳標
return handler(url).then(() => {
return index;
});
});
// 利用數組的 reduce 方法來以隊列的形式執行
return sequence.reduce((last, url, currentIndex) => {
return last.then(() => {
// 返回最快改變狀態的 Promise
return Promise.race(promises)
}).catch(err => {
// 這裏的 catch 不只用來捕獲前面 then 方法拋出的錯誤
// 更重要的是防止中斷整個鏈式調用
console.error(err)
}).then((res) => {
// 用新的 Promise 替換掉最快改變狀態的 Promise
promises[res] = handler(sequence[currentIndex]).then(() => {
return res
});
})
}, Promise.resolve()).then(() => {
return Promise.all(promises)
})
}
limitLoad(urls, loadImg, 3);
/* 由於 limitLoad 函數也返回一個 Promise,因此當 全部圖片加載完成後,能夠繼續鏈式調用 limitLoad(urls, loadImg, 3).then(() => { console.log('全部圖片加載完成'); }).catch(err => { console.error(err); }) 複製代碼
封裝一個異步加載圖片的方法
解析:
function loadImageAsync(url) {
return new Promise(function(resolve,reject) {
var image = new Image();
image.onload = function() {
resolve(image)
};
image.onerror = function() {
reject(new Error('Could not load image at' + url));
};
image.src = url;
});
}
複製代碼
這一部分題目我從網上收集來的一些練習題,幾道編程題我自已第一遍作也以爲沒那麼好作,可是作完感受獲益匪淺,加深了我對Promise特性和如何更好使用Promise的理解。
因此Promise真正的學習,仍是要結合具體場景實際開發運用。
Promise功能看似簡單,但認真整理卻發現Promise內容確實挺多的,自已花了一成天時間去查閱、學習、理解、跑代碼、而後整理promise的筆記。