題目一
const promise = new Promise((resolve, reject) => { console.log(1); resolve(); console.log(2); }) promise.then(() => { console.log(3); }) console.log(4);
解析
首先 Promise 新建后立即執行,所以會先輸出 1,2,而 Promise.then()
內部的代碼在 當次 事件循環的 結尾 立刻執行 ,所以會繼續輸出4,最后輸出3。
答案
1 2 4 3
題目二
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 函數
將 Promise 對象的狀態從“未完成”變為“成功”
(即從 pending 變為 resolved
),在異步操作成功時調用,並將異步操作的結果,作為參數傳遞出去;
reject 函數
將 Promise 對象的狀態從“未完成”變為“失敗”
(即從 pending 變為 rejected
),在異步操作失敗時調用,並將異步操作報出的錯誤,作為參數傳遞出去。
而一旦狀態改變,就不會再變。
所以 代碼中的reject('error');
不會有作用。
Promise 只能 resolve 一次,剩下的調用都會被忽略。
所以 第二次的 resolve('success2');
也不會有作用。
答案
then: success1
題目三
Promise.resolve(1) .then(2) .then(Promise.resolve(3)) .then(console.log)
解析
Promise.resolve
方法的參數如果是一個原始值,或者是一個不具有 then
方法的對象,則 Promise.resolve
方法返回一個新的 Promise
對象,狀態為resolved
,Promise.resolve
方法的參數,會同時傳給回調函數。
then
方法接受的參數是函數,而如果傳遞的並非是一個函數,它實際上會將其解釋為 then(null)
,這就會導致前一個 Promise
的結果會穿透下面。
答案
1
題目四
紅燈三秒亮一次,綠燈一秒亮一次,黃燈2秒亮一次;如何讓三個燈不斷交替重復亮燈?(用Promse實現)三個亮燈函數已經存在:
function red() { console.log('red'); } function green() { console.log('green'); } function yellow() { console.log('yellow'); }
解析
紅燈三秒亮一次,綠燈一秒亮一次,黃燈2秒亮一次,意思就是3秒,執行一次 red 函數,2秒執行一次yellow 函數,1秒執行一次 green 函數,不斷交替重復亮燈,意思就是1秒后執行green函數,在隔1秒后執行yellow函數,在隔1秒后執行red函數,重復執行這個過程,那么代碼如下。
答案
function red() { // 3秒執行一次 console.log('red'); } function green() { // 1秒執行一次 console.log('green'); } function yellow() { // 2秒執行一次 console.log('yellow'); } async function light(duration,cb) { return new Promise((resolve,reject) => { setTimeout(() => { cb(); resolve(); },duration); }) } async function step() { while(true) { await light(1000,green); await light(1000,yellow); await light(1000,red); } } step();
在擴展一個示例:我們現在要實現一個紅綠燈,把一個圓形 div 按照綠色 3 秒,黃色 1 秒,紅色 2 秒循環改變背景色。
(function (){ var btn = document.getElementById('status'); function sleep(duration){ return new Promise(function(resolve,reject){ setTimeout(resolve,duration); }) } async function changeColor(duration,color){ btn.style.background = color; await sleep(duration); }; async function fn(){ while(true){ await changeColor(3000,'green'); await changeColor(1000,'yellow'); await changeColor(2000,'red'); } } fn(); })();
題目五
實現 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]
解析
首先 ajax1 、ajax2、ajax3
都是函數,只是這些函數執行后會返回一個 Promise
,按題目的要求我們只要順序執行這三個函數就好了,然后把結果放到 data
中,但是這些函數里都是異步操作,想要按順序執行,然后輸出 1,2,3並沒有那么簡單,看個例子。
function A() { setTimeout(function () { console.log('a'); }, 3000); } function B() { setTimeout(function () { console.log('b'); }, 1000); } A(); B(); // b // a
例子中我們是按順序執行的 A
,B
但是輸出的結果卻是 b
,a
對於這些異步函數來說,並不會按順序執行完一個,再執行后一個。
這道題就是考用 Promise
控制異步流程,我們要想辦法,讓這些函數,一個執行完之后,再執行下一個,看答案吧。
答案
// 保存數組中的函數執行后的結果 var data = []; // Promise.resolve方法調用時不帶參數,直接返回一個resolved狀態的 Promise 對象。 var sequence = Promise.resolve(); ajaxArray.forEach(function (item) { // 第一次的 then 方法用來執行數組中的每個函數, // 第二次的 then 方法接受數組中的函數執行后返回的結果, // 並把結果添加到 data 中,然后把 data 返回。 // 這里對 sequence 的重新賦值,其實是相當於延長了 Promise 鏈 sequence = sequence.then(item).then(function (res) { data.push(res); return data; }); }) // 遍歷結束后,返回一個 Promise,也就是 sequence, 他的 [[PromiseValue]] 值就是 data, // 而 data(保存數組中的函數執行后的結果) 也會作為參數,傳入下次調用的 then 方法中。 return sequence;
題目六
以下代碼最后輸出什么?
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);
解析
這道題就其實和 Promise
的關系不太大,主要是需要理解 JS執行機制,才能很好的解決這道題,對於 JS 執行機制不了解的朋友推薦看看這篇文章
第一輪事件循環
先執行宏任務,主script ,new Promise立即執行,輸出【3】,
執行 p 這個new Promise 操作,輸出【7】,
發現 setTimeout,將回調放入下一輪任務隊列(Event Queue),p 的 then,姑且叫做 then1,放入微任務隊列,發現 first 的 then,叫 then2,放入微任務隊列。執行console.log(4)
,輸出【4】,宏任務執行結束。
再執行微任務,執行 then1,輸出【1】,
執行 then2,輸出【2】。
到此為止,第一輪事件循環結束。開始執行第二輪。
第二輪事件循環
先執行宏任務里面的,也就是 setTimeout 的回調,輸出【5】。resolve(6)
不會生效,因為 p 這個 Promise 的狀態一旦改變就不會在改變了。
答案
3 7 4 1 2 5
題目七
有 8 個圖片資源的 url,已經存儲在數組 urls
中(即urls = ['http://example.com/1.jpg', ...., 'http://example.com/8.jpg'])
,而且已經有一個函數 function loadImg
,輸入一個 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 = function () { 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。
答案
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 = function () { console.log('一張圖片加載完成'); resolve(); } img.onerror = reject img.src = url }) }; function limitLoad(urls, handler, limit) { // 對數組做一個拷貝 const sequence = [].concat(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); }) */