GitHub地址:limit-promise
眾所周知js是單線程得糜,并不存在真正的并發(fā)叨襟,但是由于JavaScript的Event Loop機(jī)制繁扎,使得異步函數(shù)調(diào)用有了“并發(fā)”這樣的假象。這里只是形象說(shuō)明才這么稱呼的,因此用了引號(hào)梳玫。
有關(guān)限制Promise“并發(fā)”的文章早就想寫了爹梁,記性不好老忘記。這個(gè)問(wèn)題是我在2018年某天上班時(shí)提澎,一個(gè)同事提出來(lái)的姚垃。
它的使用場(chǎng)景如限制網(wǎng)絡(luò)請(qǐng)求的數(shù)量,限制文件下載請(qǐng)求的上限等等盼忌。開(kāi)發(fā)過(guò)微信小程序的都知道积糯,網(wǎng)絡(luò)請(qǐng)求wx.request
、wx.downloadFile
等接口的最大并發(fā)限制是10谦纱。
那么我們?nèi)绾螌?shí)現(xiàn)這樣的功能看成,讓我們可以隨意調(diào)用受限制的函數(shù),而又不需要當(dāng)心它是否超過(guò)了限制跨嘉。
這里依然可以利用到任務(wù)隊(duì)列這種思想川慌,在每次要執(zhí)行“受限”任務(wù)時(shí),判斷當(dāng)前正在執(zhí)行的任務(wù)數(shù)量是否超過(guò)給定的上限祠乃,如果未超過(guò)則立即執(zhí)行這個(gè)“任務(wù)”梦重,否則進(jìn)入任務(wù)隊(duì)列中等待執(zhí)行。
由于我們經(jīng)常使用Promise作為異步編程的解決方案亮瓷,這里把異步任務(wù)封裝成一個(gè)Promise或者async函數(shù)琴拧。
class LimitPromise {
constructor (max) {
// 異步任務(wù)“并發(fā)”上限
this._max = max
// 當(dāng)前正在執(zhí)行的任務(wù)數(shù)量
this._count = 0
// 等待執(zhí)行的任務(wù)隊(duì)列
this._taskQueue = []
}
/**
* 調(diào)用器,將異步任務(wù)函數(shù)和它的參數(shù)傳入
* @param caller 異步任務(wù)函數(shù)寺庄,它必須是async函數(shù)或者返回Promise的函數(shù)
* @param args 異步任務(wù)函數(shù)的參數(shù)列表
* @returns {Promise<unknown>} 返回一個(gè)新的Promise
*/
call (caller, ...args) {
return new Promise((resolve, reject) => {
const task = this._createTask(caller, args, resolve, reject)
if (this._count >= this._max) {
// console.log('count >= max, push a task to queue')
this._taskQueue.push(task)
} else {
task()
}
})
}
/**
* 創(chuàng)建一個(gè)任務(wù)
* @param caller 實(shí)際執(zhí)行的函數(shù)
* @param args 執(zhí)行函數(shù)的參數(shù)
* @param resolve
* @param reject
* @returns {Function} 返回一個(gè)任務(wù)函數(shù)
* @private
*/
_createTask (caller, args, resolve, reject) {
return () => {
// 實(shí)際上是在這里調(diào)用了異步任務(wù)艾蓝,并將異步任務(wù)的返回(resolve和reject)拋給了上層
caller(...args)
.then(resolve)
.catch(reject)
.finally(() => {
// 任務(wù)隊(duì)列的消費(fèi)區(qū),利用Promise的finally方法斗塘,在異步任務(wù)結(jié)束后赢织,取出下一個(gè)任務(wù)執(zhí)行
this._count--
if (this._taskQueue.length) {
// console.log('a task run over, pop a task to run')
let task = this._taskQueue.shift()
task()
} else {
// console.log('task count = ', count)
}
})
this._count++
// console.log('task run , task count = ', count)
}
}
}
上述代碼內(nèi)容很少,主要的核心函數(shù)也就兩個(gè)馍盟。
- 調(diào)用器:就是把真正的執(zhí)行函數(shù)和參數(shù)傳入于置,創(chuàng)建返回一個(gè)新的Promise,而這個(gè)新Promise的什么時(shí)候返回贞岭,取決于這個(gè)異步任務(wù)何時(shí)被調(diào)度八毯。Promise內(nèi)部主要就是創(chuàng)建一個(gè)任務(wù),判斷任務(wù)是執(zhí)行還是入隊(duì)瞄桨。
- 創(chuàng)建任務(wù):實(shí)際上就是返回了一個(gè)函數(shù)话速,將真正的執(zhí)行函數(shù)放在里面執(zhí)行。這里利用了Promise的finally方法芯侥,在finally中判斷是否執(zhí)行下一個(gè)任務(wù)泊交,實(shí)現(xiàn)任務(wù)隊(duì)列連續(xù)消費(fèi)的地方就是這里乳讥。
下面舉個(gè)例子怎么使用它。假設(shè)我們有一個(gè)網(wǎng)絡(luò)請(qǐng)求模塊廓俭,叫request.js
云石,包含get
和post
方法,一般情況下研乒,是這樣使用的:
const request = require('./request')
request.get('https://www.baidu.com')
.then((res) => {
// 處理返回結(jié)果
})
.catch(err => {
// 處理異常情況
})
現(xiàn)在我們要把它改造成受限制的網(wǎng)絡(luò)請(qǐng)求汹忠,假設(shè)請(qǐng)求上限設(shè)為10個(gè),并起名叫limitRequest.js
雹熬。實(shí)現(xiàn)如下:
const LimitPromise = require('limit-promise')
const request = require('./request')
// 請(qǐng)求上限
const MAX = 10
// 核心控制器
const limitP = new LimitPromise(MAX)
// 利用核心控制器包裝request中的函數(shù)
function get (url, params) {
return limitP.call(request.get, url, params)
}
function post (url, params) {
return limitP.call(request.post, url, params)
}
// 導(dǎo)出
module.exports = {get, post}
這里就完成受限請(qǐng)求模塊的構(gòu)建了宽菜,是不是很簡(jiǎn)單,而且調(diào)用接口完全沒(méi)變橄唬,只需要引入limitRequest.js
替代原先的即可赋焕。
代碼已上傳至GitHub:limit-promise