15道ES6 Promise實戰(zhàn)練習題,助你快速理解Promise

Promise是ES6中的特性蛙酪,現(xiàn)在很多前端框架像AngularJS,Vue等在HTTP請求之后都是返回的Promise處理究履,因此Promise是必須要掌握的一個知識點滤否。
本文將為大家分享15道由易到難的ES6 Promise題, 幫助你快速理解Promise。

基礎題

01

const promise = new Promise((resolve, reject) => {
    console.log(1)
    resolve()
    console.log(2)
})
promise.then(() => {
    console.log(3)
})
console.log(4)

解析:
Promise 構(gòu)造函數(shù)是同步執(zhí)行的最仑,promise.then 中的函數(shù)是異步執(zhí)行的藐俺。
運行結(jié)果:

// => 1
// => 2
// => 4
// => 3

02

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執(zhí)行機制。
第一輪事件循環(huán)泥彤,先執(zhí)行宏任務欲芹,主script,new Promise立即執(zhí)行吟吝,輸出 3菱父,執(zhí)行p這個new Promise操作,輸出 7剑逃,發(fā)現(xiàn)setTimeout浙宜,將回調(diào)函數(shù)放入下一輪任務隊列(Event Quene),p的then蛹磺,暫且命名為then1粟瞬,放入微任務隊列,且first也有then萤捆,命名為then2裙品,放入微任務隊列。執(zhí)行console.log(4),輸出 4俗或,宏任務執(zhí)行結(jié)束市怎。
再執(zhí)行微任務,執(zhí)行then1,輸出 1辛慰,執(zhí)行then2,輸出 3.
第一輪事件循環(huán)結(jié)束区匠,開始執(zhí)行第二輪。第二輪事件循環(huán)先執(zhí)行宏任務里面的帅腌,也就是setTimeout的回調(diào)驰弄,輸出 5.resolve(6)不會生效蝠筑,因為p的Promise狀態(tài)一旦改變就不會再變化了。
運行結(jié)果:

// => 3
// => 7
// => 4
// => 1
// => 2
// => 5

03

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)

運行結(jié)果:

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 種狀態(tài):pending揩懒、fulfilled 或 rejected。狀態(tài)改變只能是 pending->fulfilled 或者 pending->rejected挽封,狀態(tài)一旦改變則不能再變已球。上面 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)
  })

解析:
構(gòu)造函數(shù)中的 resolve 或 reject 只有第一次執(zhí)行有效智亮,多次調(diào)用沒有任何作用,呼應代碼二結(jié)論:promise 狀態(tài)一旦改變則不能再變点待。
運行結(jié)果:

then: success1

05

Promise.resolve(1)
  .then((res) => {
    console.log(res)
    return 2
  })
  .catch((err) => {
    return 3
  })
  .then((res) => {
    console.log(res)
  })

解析:
promise 可以鏈式調(diào)用阔蛉。提起鏈式調(diào)用我們通常會想到通過 return this 實現(xiàn),不過 Promise 并不是這樣實現(xiàn)的癞埠。promise 每次調(diào)用 .then 或者 .catch 都會返回一個新的 promise状原,從而實現(xiàn)了鏈式調(diào)用。
運行結(jié)果:

1
2

06

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 可以被調(diào)用多次苗踪,但這里 Promise 構(gòu)造函數(shù)只執(zhí)行一次颠区。或者說 promise 內(nèi)部狀態(tài)一經(jīng)改變通铲,并且有了一個值毕莱,那么后續(xù)每次調(diào)用 .then 或者 .catch 都會直接拿到該值。
運行結(jié)果:

once
success 1005
success 1007

07

Promise.resolve()
  .then(() => {
    return new Error('error!!!')
  })
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })

解析:
.then 或者 .catch 中 return 一個 error 對象并不會拋出錯誤颅夺,所以不會被后續(xù)的 .catch 捕獲朋截,需要改成其中一種:
return Promise.reject(new Error('error!!!'))
throw new Error('error!!!')
因為返回任意一個非 promise 的值都會被包裹成 promise 對象,即 return new Error('error!!!') 等價于 return Promise.resolve(new Error('error!!!'))吧黄。
運行結(jié)果:

then:  Error: error!!!
    at <anonymous>

08

const promise = Promise.resolve()
  .then(() => {
    return promise
  })
promise.catch(console.error)

解析:.then 或 .catch 返回的值不能是 promise 本身部服,否則會造成死循環(huán)。類似于:

process.nextTick(function tick () {
  console.log('tick')
  process.nextTick(tick)
})

運行結(jié)果:

TypeError: Chaining cycle detected for promise #<Promise>

09

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)

解析:
.then 或者 .catch 的參數(shù)期望是函數(shù)稚字,傳入非函數(shù)則會發(fā)生值穿透饲宿。
運行結(jié)果:

1

10

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 可以接收兩個參數(shù),第一個是處理成功的函數(shù)胆描,第二個是處理錯誤的函數(shù)瘫想。
.catch 是 .then 第二個參數(shù)的簡便寫法,但是它們用法上有一點需要注意:.then 的第二個處理錯誤的函數(shù)捕獲不了第一個處理成功的函數(shù)拋出的錯誤昌讲,而后續(xù)的 .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)
  })

運行結(jié)果:

fail2:  Error: error
    at success (<anonymous>)

11

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短绸,在事件循環(huán)的 check 階段執(zhí)行车吹。事件循環(huán)的每個階段(macrotask)之間都會執(zhí)行 microtask筹裕,事件循環(huán)的開始會先執(zhí)行一次 microtask。
運行結(jié)果:

end
nextTick
then
setImmediate

12
紅燈3秒亮一次窄驹,綠燈1秒亮一次朝卒,黃燈2秒亮一次;如何使用Promise讓三個燈不斷交替重復亮燈乐埠?

function red(){
    console.log('red');
}
function green(){
    console.log('green');
}
function yellow(){
    console.log('yellow');
}

分析:
先看題目抗斤,題目要求紅燈亮過后,綠燈才能亮丈咐,綠燈亮過后瑞眼,黃燈才能亮,黃燈亮過后棵逊,紅燈才能亮……所以怎么通過Promise實現(xiàn)伤疙?
換句話說,就是紅燈亮起時辆影,承諾2s秒后亮綠燈徒像,綠燈亮起時承諾1s后亮黃燈,黃燈亮起時蛙讥,承諾3s后亮紅燈……這顯然是一個Promise鏈式調(diào)用厨姚,看到這里你心里或許就有思路了,我們需要將我們的每一個亮燈動作寫在then()方法中键菱,同時返回一個新的Promise谬墙,并將其狀態(tài)由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

13
請實現(xiàn)一個mergePromise函數(shù)拭抬,把傳進去的數(shù)組按順序先后執(zhí)行,并且把返回的數(shù)據(jù)先后放到數(shù)組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 => {
    // 在這里實現(xiàn)你的代碼

};

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都是函數(shù)算凿,只是這些函數(shù)執(zhí)行后會返回一個Promise,按照題目要求只要順序執(zhí)行這三個函數(shù)就好了犁功,然后把結(jié)果放到data中;
答案:

const mergePromise = ajaxArray => {
  // 在這里實現(xiàn)你的代碼
  // 保存數(shù)組中的函數(shù)執(zhí)行后的結(jié)果
  var data = [];

  // Promise.resolve方法調(diào)用時不帶參數(shù)氓轰,直接返回一個resolved狀態(tài)的 Promise 對象。
  var sequence = Promise.resolve();

  ajaxArray.forEach(item => {
    // 第一次的 then 方法用來執(zhí)行數(shù)組中的每個函數(shù)浸卦,
    // 第二次的 then 方法接受數(shù)組中的函數(shù)執(zhí)行后返回的結(jié)果署鸡,
    // 并把結(jié)果添加到 data 中,然后把 data 返回。
    sequence = sequence.then(item).then(res => {
      data.push(res);
      return data;
    });
  });

// 遍歷結(jié)束后靴庆,返回一個 Promise时捌,也就是 sequence, 他的 [[PromiseValue]] 值就是 data炉抒,
// 而 data(保存數(shù)組中的函數(shù)執(zhí)行后的結(jié)果) 也會作為參數(shù)奢讨,傳入下次調(diào)用的 then 方法中。
  return sequence;
};

14
現(xiàn)有8個圖片資源的url焰薄,已經(jīng)存儲在數(shù)組urls中禽笑,且已有一個函數(shù)function loading,輸入一個url鏈接蛤奥,返回一個Promise,該Promise在圖片下載完成的時候resolve僚稿,下載失敗則reject凡桥。
要求:任何時刻同時下載的鏈接數(shù)量不可以超過3個。
請寫一段代碼實現(xiàn)這個需求蚀同,要求盡可能快速地將所有圖片下載完成缅刽。

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;
    })
};

解析
題目的意思是需要先并發(fā)請求3張圖片,當一張圖片加載完成后蠢络,又會繼續(xù)發(fā)起一張圖片的請求衰猛,讓并發(fā)數(shù)保持在3個,直到需要加載的圖片都全部發(fā)起請求刹孔。
用Promise來實現(xiàn)就是啡省,先并發(fā)請求3個圖片資源,這樣可以得到3個Promise髓霞,組成一個數(shù)組promises卦睹,然后不斷調(diào)用Promise.race來返回最快改變狀態(tài)的Promise,然后從數(shù)組promises中刪掉這個Promise對象方库,再加入一個新的Promise结序,直到全部的url被取完暂殖,最后再使用Promise.all來處理一遍數(shù)組promises中沒有改變狀態(tài)的Promise抵乓。

function limitLoad(urls, handler, limit) {
  // 對數(shù)組做一個拷貝
    const sequence = […urls];
    let promises = [];
 //并發(fā)請求到最大數(shù)
  promises = sequence.splice(0, limit).map((url, index) => {
    // 這里返回的 index 是任務在 promises 的腳標,用于在 Promise.race 之后找到完成的任務腳標
    return handler(url).then(() => {
      return index;
    });
  });

  // 利用數(shù)組的 reduce 方法來以隊列的形式執(zhí)行
  return sequence.reduce((last, url, currentIndex) => {
    return last.then(() => {
      // 返回最快改變狀態(tài)的 Promise
      return Promise.race(promises)
    }).catch(err => {
      // 這里的 catch 不僅用來捕獲前面 then 方法拋出的錯誤
      // 更重要的是防止中斷整個鏈式調(diào)用
      console.error(err)
    }).then((res) => {
      // 用新的 Promise 替換掉最快改變狀態(tài)的 Promise
      promises[res] = handler(sequence[currentIndex]).then(() => {
        return res
      });
    })
  }, Promise.resolve()).then(() => {
    return Promise.all(promises)
  })

}

limitLoad(urls, loadImg, 3);

/*
因為 limitLoad 函數(shù)也返回一個 Promise颂碘,所以當 所有圖片加載完成后邀层,可以繼續(xù)鏈式調(diào)用

limitLoad(urls, loadImg, 3).then(() => {
    console.log('所有圖片加載完成');
}).catch(err => {
    console.error(err);
})
*/

15
封裝一個異步加載圖片的方法
解析:

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;
     });
}
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末返敬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子寥院,更是在濱河造成了極大的恐慌救赐,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異经磅,居然都是意外死亡泌绣,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門预厌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阿迈,“玉大人,你說我怎么就攤上這事轧叽∶绮祝” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵炭晒,是天一觀的道長待逞。 經(jīng)常有香客問我,道長网严,這世上最難降的妖魔是什么识樱? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮震束,結(jié)果婚禮上怜庸,老公的妹妹穿的比我還像新娘。我一直安慰自己垢村,他們只是感情好割疾,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嘉栓,像睡著了一般宏榕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上侵佃,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天担扑,我揣著相機與錄音,去河邊找鬼趣钱。 笑死涌献,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的首有。 我是一名探鬼主播燕垃,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼井联!你這毒婦竟也來了卜壕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤烙常,失蹤者是張志新(化名)和其女友劉穎轴捎,沒想到半個月后鹤盒,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡侦副,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年侦锯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秦驯。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡尺碰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出译隘,到底是詐尸還是另有隱情亲桥,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布固耘,位于F島的核電站题篷,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏厅目。R本人自食惡果不足惜番枚,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望璧瞬。 院中可真熱鬧渐夸,春花似錦墓塌、人聲如沸苫幢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽剩蟀。三九已至育特,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間犬缨,已是汗流浹背遍尺。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工乾戏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鼓择,地道東北人呐能。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓朗徊,卻偏偏與公主長得像爷恳,于是被迫代替她去往敵國和親温亲。 傳聞我的和親對象是個殘疾皇子栈虚,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容