簡述
當我們需要保證代碼在多個異步操作都完成後執行,通常我們會使用Promise.all 來實現。以請求多張圖片爲例:
// 爲了演示方便,我們在此用fetchImage函數來模擬異步請求圖片,返回成功提示
function fetchImage(url) {
// 模擬請求的響應時間在0 - 1s之間隨機
const timeCost = Math.random() * 1000
return new Promise(resolve => setTimeout(resolve, timeCost, 'get: ' + url))
}
// 待請求的圖片
const imageUrls = [
'pic_1.png',
'pic_2.png',
'pic_3.png',
'pic_4.png',
'pic_5.png',
'pic_6.png',
]
Promise
.all(imageUrls.map(url => fetchImage(url)))
.then(resList => console.log(resList))
輸出爲
[
"get: pic_1.png",
"get: pic_2.png",
"get: pic_3.png",
"get: pic_4.png",
"get: pic_5.png",
"get: pic_6.png"
]
如果我們對並行的請求數量有限制,Promise.all
自身是不具有這個功能的。
那麼接下來,我們依舊以上述的fetchImage
爲例,來實現一個可以對圖片請求進行並行限制的函數:
/**
* @description 帶併發限制的圖片併發請求
* @param {Array} imageUrls 待請求的圖片url列表
* @param {Object} limit 最大併發個數限制
* @return { Promise<Array> } resList
*/
function fetchImageWithLimit(imageUrls, limit = 4)
本文提供兩個思路解決該問題:
- 通過
Promise.all
來實現 - 通過
Promise.race
來實現
通過Promise.all實現
步驟如下:
- 初始化
limit
個Promise對象,作爲Promise.all
的參數 - 每個Promise對象去
imageUrls
中取出一個url進行請求,若無則resolve - 每個Promise對象在當前請求成功後重復步驟2
function fetchImageWithLimit(imageUrls, limit) {
// copy一份,作爲剩餘url的記錄
let urls =[ ...imageUrls ]
// 用來記錄url - response 的映射
// 保證輸出列表與輸入順序一致
let rs = new Map()
// 遞歸的去取url進行請求
function run() {
if(urls.length > 0) {
// 取一個,便少一個
const url = urls.shift()
// console.log(url, ' [start at] ', ( new Date()).getTime() % 10000)
return fetchImage(url).then(res => {
// console.log(url, ' [end at] ', ( new Date()).getTime() % 10000)
rs.set(url, res)
return run()
})
}
}
// 當imageUrls.length < limit的時候,我們也沒有必要去創建多餘的Promise
const promiseList = Array(Math.min(limit, imageUrls.length))
// 這裏用Array.protetype.fill做了簡寫,但不能進一步簡寫成.fill(run())
.fill(Promise.resolve())
.map(promise => promise.then(run))
return Promise.all(promiseList).then(() => imageUrls.map(item => rs.get(item)))
}
去掉代碼中console.log
,可展示出如下結果:
fetchImageWithLimit(imageUrls, 2)
.then(res => console.log(res))
.catch(err => console.error(err))
// 過程中輸出依次爲(次序每次都可能不一樣)
pic_1.png [start at] 746
pic_2.png [start at] 746
pic_1.png [end at] 814
pic_3.png [start at] 814
pic_3.png [end at] 1329
pic_4.png [start at] 1330
pic_2.png [end at] 1460
pic_5.png [start at] 1460
pic_4.png [end at] 2172
pic_6.png [start at] 2172
pic_5.png [end at] 2401
pic_6.png [end at] 2968
// 最終的結果輸出
[
"get: pic_1.png",
"get: pic_2.png",
"get: pic_3.png",
"get: pic_4.png",
"get: pic_5.png",
"get: pic_6.png"
]
通過Promise.race實現
步驟如下:
- 初始化
limit
個圖片請求,作爲Promise.race
的參數 - 當其中任一請求結束,即新建一個圖片請求(還有的話),並連同之前未結束的
limit - 1
個圖片請求一起作爲新一輪的Promise.race
的參數 - 重複步驟2,直到圖片都已被請求
具體可參考 這篇博客中對於asyncPool
實現的分析。
我覺得寫的挺好,有空了我再來自己手撕一遍。~。~