js如何限制Promise“并發(fā)”的數(shù)量

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.requestwx.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云石,包含getpost方法,一般情況下研乒,是這樣使用的:

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市仰楚,隨后出現(xiàn)的幾起案子隆判,更是在濱河造成了極大的恐慌,老刑警劉巖僧界,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侨嘀,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡捂襟,警方通過(guò)查閱死者的電腦和手機(jī)咬腕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)葬荷,“玉大人涨共,你說(shuō)我怎么就攤上這事〕桎觯” “怎么了举反?”我有些...
    開(kāi)封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)扒吁。 經(jīng)常有香客問(wèn)我火鼻,道長(zhǎng),這世上最難降的妖魔是什么雕崩? 我笑而不...
    開(kāi)封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任魁索,我火速辦了婚禮,結(jié)果婚禮上盼铁,老公的妹妹穿的比我還像新娘粗蔚。我一直安慰自己,他們只是感情好饶火,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布支鸡。 她就那樣靜靜地躺著冬念,像睡著了一般趁窃。 火紅的嫁衣襯著肌膚如雪牧挣。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天醒陆,我揣著相機(jī)與錄音瀑构,去河邊找鬼。 笑死刨摩,一個(gè)胖子當(dāng)著我的面吹牛寺晌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播澡刹,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼呻征,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了罢浇?” 一聲冷哼從身側(cè)響起陆赋,我...
    開(kāi)封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嚷闭,沒(méi)想到半個(gè)月后攒岛,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡胞锰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年灾锯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嗅榕。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡顺饮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出凌那,到底是詐尸還是另有隱情兼雄,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布案怯,位于F島的核電站君旦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏嘲碱。R本人自食惡果不足惜金砍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望麦锯。 院中可真熱鬧恕稠,春花似錦、人聲如沸扶欣。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至骆捧,卻和暖如春澎羞,著一層夾襖步出監(jiān)牢的瞬間敛苇,已是汗流浹背妆绞。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留枫攀,地道東北人括饶。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像来涨,于是被迫代替她去往敵國(guó)和親图焰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

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