Promise面試題詳解之控制并發(fā)
前言
在寫(xiě)這篇文章的時(shí)候我有點(diǎn)猶豫,因?yàn)橄惹皩?xiě)過(guò)一篇類(lèi)似的,一道關(guān)于并發(fā)控制的面試題,只不過(guò)那篇文章只給出了一種解決方案,后來(lái)在網(wǎng)上又陸續(xù)找到兩種解決方案,說(shuō)來(lái)慚愧,研究問(wèn)題總是淺嘗輒止,所以今天便放在一起,借著這道面試題再重新梳理一下。
題目是這樣的:
有 8 個(gè)圖片資源的 url,已經(jīng)存儲(chǔ)在數(shù)組 urls 中(即urls = [‘http://example.com/1.jpg', …., ‘http://example.com/8.jpg']),而且已經(jīng)有一個(gè)函數(shù) function loadImg,輸入一個(gè) url 鏈接,返回一個(gè) Promise,該 Promise 在圖片下載完成的時(shí)候 resolve,下載失敗則 reject。
但是我們要求,任意時(shí)刻,同時(shí)下載的鏈接數(shù)量不可以超過(guò) 3 個(gè)。
請(qǐng)寫(xiě)一段代碼實(shí)現(xiàn)這個(gè)需求,要求盡可能快速地將所有圖片下載完成。
已有代碼如下:
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
})
};
看到這個(gè)題目的時(shí)候,腦袋里瞬間想到了高效率排隊(duì)買(mǎi)地鐵票的情景,那個(gè)情景類(lèi)似下圖:

上圖這樣的排隊(duì)和并發(fā)請(qǐng)求的場(chǎng)景基本類(lèi)似,窗口只有三個(gè),人超過(guò)三個(gè)之后,后面的人只能排隊(duì)了。
首先想到的便是利用遞歸來(lái)做,就如這篇文章采取的措施一樣,代碼如下:
//省略代碼
var count = 0;
//對(duì)加載圖片的函數(shù)做處理,計(jì)數(shù)器疊加計(jì)數(shù)
function bao(){
count++;
console.log("并發(fā)數(shù):",count)
//條件判斷,urls長(zhǎng)度大于0繼續(xù),小于等于零說(shuō)明圖片加載完成
if(urls.length>0&&count<=3){
//shift從數(shù)組中取出連接
loadImg(urls.shift()).then(()=>{
//計(jì)數(shù)器遞減
count--
//遞歸調(diào)用
}).then(bao)
}
}
function async1(){
//循環(huán)開(kāi)啟三次
for(var i=0;i<3;i++){
bao();
}
}
async1()
以上是最常規(guī)的思路,我將加載圖片的函數(shù)loadImg封裝在bao函數(shù)內(nèi),根據(jù)條件判斷,是否發(fā)送請(qǐng)求,請(qǐng)求完成后繼續(xù)遞歸調(diào)用。
以上代碼所有邏輯都寫(xiě)在了同一個(gè)函數(shù)中然后遞歸調(diào)用,可以?xún)?yōu)化一下,代碼如下:
var count = 0;
// 封裝請(qǐng)求的異步函數(shù),增加計(jì)數(shù)器功能
function request(){
count++;
loadImg(urls.shift()).then(()=>{
count--
}).then(diaodu)
}
// 負(fù)責(zé)調(diào)度的函數(shù)
function diaodu(){
if(urls.length>0&&count<=3){
request();
}
}
function async1(){
for(var i=0;i<3;i++){
request();
}
}
async1()
上面代碼將一個(gè)遞歸函數(shù)拆分成兩個(gè),一個(gè)函數(shù)只負(fù)責(zé)計(jì)數(shù)和發(fā)送請(qǐng)求,另外一個(gè)負(fù)責(zé)調(diào)度。
這里的請(qǐng)求既然已經(jīng)被封裝成了Promise,那么我們用Promise和saync、await來(lái)完成一下,代碼如下:
//省略代碼
// 計(jì)數(shù)器
var count = 0;
// 全局鎖
var lock = [];
var l = urls.length;
async function bao(){
if(count>=3){
//超過(guò)限制利用await和promise進(jìn)行阻塞;
let _resolve;
await new Promise((resolve,reject)=>{
_resolve=resolve;
// resolve不執(zhí)行,將其推入lock數(shù)組;
lock.push(_resolve);
});
}
if(urls.length>0){
console.log(count);
count++
await loadImg(urls.shift());
count--;
lock.length&&lock.shift()()
}
}
for (let i = 0; i < l; i++) {
bao();
}
大致思路是,遍歷執(zhí)行urls.length長(zhǎng)度的請(qǐng)求,但是當(dāng)請(qǐng)求并發(fā)數(shù)大于限制時(shí),超過(guò)的請(qǐng)求用await結(jié)合promise將其阻塞,并且將resolve填充到lock數(shù)組中,繼續(xù)執(zhí)行,并發(fā)過(guò)程中有圖片加載完成后,從lock中推出一項(xiàng)resolve執(zhí)行,lock相當(dāng)于一個(gè)叫號(hào)機(jī);
以上代碼可以?xún)?yōu)化為:
// 計(jì)數(shù)器
var count = 0;
// 全局鎖
var lock = [];
var l = urls.length;
// 阻塞函數(shù)
function block(){
let _resolve;
return new Promise((resolve,reject)=>{
_resolve=resolve;
// resolve不執(zhí)行,將其推入lock數(shù)組;
lock.push(_resolve);
});
}
// 叫號(hào)機(jī)
function next(){
lock.length&&lock.shift()()
}
async function bao(){
if(count>=3){
//超過(guò)限制利用await和promise進(jìn)行阻塞;
await block();
}
if(urls.length>0){
console.log(count);
count++
await loadImg(urls.shift());
count--;
next()
}
}
for (let i = 0; i < l; i++) {
bao();
}
最后一種方案,也是我十分喜歡的,思考好久才明白,大概思路如下:
用 Promise.race來(lái)實(shí)現(xiàn),先并發(fā)請(qǐng)求3個(gè)圖片資源,這樣可以得到 3 個(gè) Promise實(shí)例,組成一個(gè)數(shù)組promises ,然后不斷的調(diào)用 Promise.race 來(lái)返回最快改變狀態(tài)的 Promise,然后從數(shù)組(promises )中刪掉這個(gè) Promise 對(duì)象實(shí)例,再加入一個(gè)新的 Promise實(shí)例,直到全部的 url 被取完。
代碼如下:
//省略代碼
function limitLoad(urls, handler, limit) {
// 對(duì)數(shù)組做一個(gè)拷貝
const sequence = [].concat(urls)
let promises = [];
//并發(fā)請(qǐng)求到最大數(shù)
promises = sequence.splice(0, limit).map((url, index) => {
// 這里返回的 index 是任務(wù)在 promises 的腳標(biāo),
//用于在 Promise.race 之后找到完成的任務(wù)腳標(biāo)
return handler(url).then(() => {
return index
});
});
(async function loop() {
let p = Promise.race(promises);
for (let i = 0; i < sequence.length; i++) {
p = p.then((res) => {
promises[res] = handler(sequence[i]).then(() => {
return res
});
return Promise.race(promises)
})
}
})()
}
limitLoad(urls, loadImg, 3)
第三種方案的巧妙之處,在于使用了Promise.race。并且在循環(huán)時(shí)用then鏈串起了執(zhí)行順序。
以上便是關(guān)于并發(fā)控制的一點(diǎn)點(diǎn)思考,有使用promise的,有不使用promise的,關(guān)鍵在于靈活運(yùn)用,通過(guò)這次梳理,你有哪些思考呢
總結(jié)
到此這篇關(guān)于Promise面試題詳解之控制并發(fā)的文章就介紹到這了,更多相關(guān)Promise控制并發(fā)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS實(shí)現(xiàn)頁(yè)面內(nèi)跳轉(zhuǎn)的簡(jiǎn)單代碼
這篇文章主要介紹了JS實(shí)現(xiàn)頁(yè)面內(nèi)跳轉(zhuǎn)的簡(jiǎn)單代碼,需要的朋友可以參考下2017-09-09
解析JavaScript中的不可見(jiàn)數(shù)據(jù)類(lèi)型
這篇文章主要是對(duì)JavaScript中的不可見(jiàn)數(shù)據(jù)類(lèi)型進(jìn)行了詳細(xì)的介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-12-12
jQuery實(shí)現(xiàn)手風(fēng)琴特效
這篇文章主要為大家詳細(xì)介紹了前端js實(shí)現(xiàn)手風(fēng)琴效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-01-01
利用JavaScript中的高階函數(shù)和閉包實(shí)現(xiàn)命令模式
命令模式提供了一種優(yōu)雅的解決方案,使得我們能夠靈活地封裝和管理代碼操作,所以本文將為大家介紹命令模式的概念、應(yīng)用場(chǎng)景以及在JavaScript中的實(shí)現(xiàn)方式,需要的可以參考一下2023-06-06
javascript和HTML5利用canvas構(gòu)建猜牌游戲?qū)崿F(xiàn)算法
讓我猜猜你心中的牌,先隨機(jī)生成27張牌,不能重復(fù)列出三列牌,然后記住其中一張,然后點(diǎn)擊牌所在的列,多次就可以猜出你想的牌,具體實(shí)現(xiàn)如下,感興趣的朋友可以參考下哈2013-07-07
web3.js調(diào)用鏈上的方法操作NFT區(qū)塊鏈MetaMask詳解
這篇文章主要為大家介紹了web3.js調(diào)用鏈上的方法操作NFT區(qū)塊鏈MetaMask詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
小程序兼容安卓和IOS數(shù)據(jù)處理問(wèn)題及坑
這篇文章主要介紹了小程序兼容安卓和IOS數(shù)據(jù)處理問(wèn)題及坑,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-09-09
解決window.history.back()返回上一頁(yè)有時(shí)候需要點(diǎn)擊多次問(wèn)題
這篇文章主要介紹了解決window.history.back()返回上一頁(yè)有時(shí)候需要點(diǎn)擊多次問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
用javascript實(shí)現(xiàn)讀取txt文檔的腳本
用javascript實(shí)現(xiàn)讀取txt文檔的腳本...2007-07-07

