俗話說得好,一動不動是王八,上一篇文章學習了那麼久Promise,是時候大顯身手了!javascript
const promise = new Promise((resolve, reject) => { console.log(1) resolve() console.log(2) }) promise.then(() => { console.log(3) }) console.log(4)
解析:
Promise 構造函數是同步執行的,promise.then 中的函數是異步執行的。前端
運行結果:java
// => 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執行機制。ajax
第一輪事件循環,先執行宏任務,主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.segmentfault
第一輪事件循環結束,開始執行第二輪。第二輪事件循環先執行宏任務裏面的,也就是setTimeout的回調,輸出 5.resolve(6)不會生效,由於p的Promise狀態一旦改變就不會再變化了。數組
運行結果: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)
運行結果:閉包
promise1 Promise {<pending>} promise2 Promise {<pending>} Uncaught (in promise) Error: error!!! at <anonymous> promise1 Promise {<resolved>: "success"} promise2 Promise {<rejected>: Error: error!!! at <anonymous>}
解釋:promise 有 3 種狀態:pending、fulfilled 或 rejected。狀態改變只能是 pending->fulfilled 或者 pending->rejected,狀態一旦改變則不能再變。上面 promise2 並非 promise1,而是返回的一個新的 Promise 實例。併發
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,而後從數組promises中刪掉這個Promise對象,再加入一個新的Promise,直到所有的url被取完,最後再使用Promise.all來處理一遍數組promises中沒有改變狀態的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的筆記。
感謝閱讀!
推薦閱讀:
【專題:JavaScript進階之路】
JavaScript之深刻理解閉包
ES6 尾調用和尾遞歸
Git經常使用命令小結
JavaScript之call()理解
JavaScript之對象屬性
我是Cloudy,年輕的前端攻城獅一枚,愛專研,愛技術,愛分享。
我的筆記,整理不易,感謝閱讀、點贊和收藏。
文章有任何問題歡迎你們指出,也歡迎你們一塊兒交流前端各類問題!