實(shí)現(xiàn)一個(gè)簡(jiǎn)單的并發(fā)請(qǐng)求隊(duì)列

前言

最近維護(hù)一個(gè)老項(xiàng)目中的微信公眾號(hào)h5的新需求,項(xiàng)目是Node.js服務(wù)溪椎,Node層負(fù)責(zé)前端路由宴凉、api聚合以及用戶信息校驗(yàn)等工作誊锭,項(xiàng)目較大,上一次維護(hù)已經(jīng)是3年前弥锄,現(xiàn)在不方便重構(gòu)整個(gè)項(xiàng)目炉旷,新需求也依賴Node層做路由和api管理,因此需要在原項(xiàng)目?jī)?nèi)做開發(fā)叉讥。

原項(xiàng)目的前端資源由fis3輸出到指定目錄窘行,其中js、css图仓、img資源會(huì)上傳至cdn罐盔,由另一臺(tái)服務(wù)器nginx代理(文件上傳服務(wù)會(huì)由另一個(gè)Node服務(wù)處理),Node層只負(fù)責(zé)解析html的解析救崔,以減少Node層業(yè)務(wù)的壓力惶看。新需求基于Vue開發(fā),需要webpack打包六孵,因此需要通過其他方法上傳靜態(tài)資源到nginx的服務(wù)器纬黎。

文件上傳請(qǐng)求是一個(gè)異步操作,那么是否可以通過并發(fā)請(qǐng)求劫窒,加快上傳的速度呢本今?新需求是多頁面,資源有上百個(gè)文件主巍,同時(shí)并發(fā)上百個(gè)請(qǐng)求會(huì)對(duì)服務(wù)器造成過大壓力冠息,我們需要一定的機(jī)制讓請(qǐng)求排隊(duì),除此以外孕索,當(dāng)上傳失敗時(shí)逛艰,還需要一定的重試能力。面對(duì)這些問題搞旭,接下來研究一下如何實(shí)現(xiàn)上述的需求散怖。

嘗試與學(xué)習(xí)

這里我參考了一篇并發(fā)請(qǐng)求的實(shí)踐,可以先了解一下原文:

不到50行代碼實(shí)現(xiàn)一個(gè)能對(duì)請(qǐng)求并發(fā)數(shù)做限制的通用RequestDecorator - 作者:陳紀(jì)庚

在大佬的基礎(chǔ)上做了一些簡(jiǎn)單的改造肄渗,增加了重試的功能镇眷,同時(shí)項(xiàng)目開發(fā)中也遇到了一些小問題,以下是具體的實(shí)現(xiàn)恳啥,有注釋說明:

// 任務(wù)隊(duì)列
class RequestQueue {
  constructor(maxLimit = 5, retry = 2) {
    // 最大并發(fā)量
    this.maxLimit = maxLimit
    // 重試次數(shù)
    this.retry = retry

    // blocking queue 若當(dāng)前請(qǐng)求并發(fā)量已經(jīng)超過maxLimit偏灿,則將請(qǐng)求延遲到下某個(gè)任務(wù)完成丹诀,再執(zhí)行該隊(duì)列任務(wù)
    this.requestQueue = []
    // 當(dāng)前并發(fā)量數(shù)目
    this.currentConcurrent = 0
    
    // 說明1:
    // 實(shí)際請(qǐng)求中钝的,可能會(huì)異步的拋出多個(gè)error
    // 任務(wù)重試過程中翁垂,當(dāng)catch到 error且 重試已到上限,會(huì)執(zhí)行 next() 執(zhí)行下一個(gè)任務(wù)硝桩,
    // 此時(shí)沿猜,如果有異常拋出前一個(gè)異步任務(wù)的,會(huì)無法捕獲 
    // 因此通過全局時(shí)間捕獲剩余的異步異常
    process.on("unhandledRejection", function(e){
      console.log(e);
    })
  }

  async run(request) {
    // 并發(fā)限制
    if (this.currentConcurrent >= this.maxLimit) {
      await this.startBlocking() // 等待執(zhí)行碗脊,直到某個(gè)任務(wù)執(zhí)行this.next()
    }
    // 隊(duì)列+1
    this.currentConcurrent++

    // 設(shè)置隊(duì)列中同一個(gè)任務(wù)嘗試次數(shù)
    for (let retryCount = this.retry; retryCount > 0; retryCount--) {
      let done = false
      console.log('[ retryCount ]:' + retryCount)
      try {
        // 這里與大佬的方法有不同啼肩,這里需要傳入一個(gè)包裝好請(qǐng)求的Promise實(shí)例,如有需要也可以用pify將請(qǐng)求轉(zhuǎn)成promise
        const result = await request() 
        // 執(zhí)行成功則結(jié)束嘗試
        done = true
        return Promise.resolve(result)
        // 如果有錯(cuò)誤衙伶,會(huì)被捕獲祈坠,不會(huì)執(zhí)行resolve
      } catch (error) {
        console.log('[ request error ] - ' + error)
        // 最后一次重試失敗時(shí)停止重試,返回報(bào)錯(cuò)
        if (retryCount === 1) {
          done = true
          return Promise.reject(error) // 錯(cuò)誤只會(huì)拋出一次
        }
      } finally {
        // 如果已經(jīng)結(jié)束重試矢劲,執(zhí)行請(qǐng)求隊(duì)列的下一個(gè)任務(wù)
        if (done) {
          this.currentConcurrent--
          this.next()
          break;
        }
      }
    }
  }

  next() {
    if (this.requestQueue.length <= 0) return
    const resolve = this.requestQueue.shift()
    resolve()  // 取出block promise 的resolve 執(zhí)行
  }

  startBlocking() {
    let _resolve
    let promise2 = new Promise((resolve) => (_resolve = resolve))
    this.requestQueue.push(_resolve)
    return promise2 // 返回block promise 用于暫停隊(duì)列的執(zhí)行
  }
}

使用方式:

const request = () => {
    return new Promise((resolve, reject) => {
     setTimeout(() => { resolve() }, 1000)
   })
}

const instance = new RequestQueue()

const promises = []
for (let i = 0; i < 100; i++ ) {
  promises.push(instance.run(request)
    .catch(err => {
        // 這里是否catch(err)取決于是否允許某個(gè)任務(wù)失敗時(shí)赦拘,其他任務(wù)繼續(xù)執(zhí)行
       console.log(err)
    })
  )
}

Promise.all(promises)
  .catch(err => console.log(err)) 
  // 如果前面的push過程中不catch,則一旦有任務(wù)拋出錯(cuò)誤芬沉,剩余的任務(wù)不再執(zhí)行

整個(gè)實(shí)現(xiàn)如上躺同,與大佬的實(shí)踐略有不同,僅供學(xué)習(xí)丸逸。實(shí)際生產(chǎn)中蹋艺,更推薦使用開源社區(qū)成熟的庫,async黄刚,這個(gè)庫提供更全面的異步流控制捎谨,便于我們進(jìn)行開發(fā)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末憔维,一起剝皮案震驚了整個(gè)濱河市侍芝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌埋同,老刑警劉巖州叠,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異凶赁,居然都是意外死亡咧栗,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門虱肄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來致板,“玉大人,你說我怎么就攤上這事咏窿≌寤颍” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵集嵌,是天一觀的道長(zhǎng)萝挤。 經(jīng)常有香客問我御毅,道長(zhǎng),這世上最難降的妖魔是什么怜珍? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任端蛆,我火速辦了婚禮,結(jié)果婚禮上酥泛,老公的妹妹穿的比我還像新娘今豆。我一直安慰自己,他們只是感情好柔袁,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布呆躲。 她就那樣靜靜地躺著,像睡著了一般捶索。 火紅的嫁衣襯著肌膚如雪歼秽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天情组,我揣著相機(jī)與錄音燥筷,去河邊找鬼。 笑死院崇,一個(gè)胖子當(dāng)著我的面吹牛肆氓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播底瓣,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼谢揪,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了捐凭?” 一聲冷哼從身側(cè)響起拨扶,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎茁肠,沒想到半個(gè)月后患民,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡垦梆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年匹颤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片托猩。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡印蓖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出京腥,到底是詐尸還是另有隱情赦肃,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站他宛,受9級(jí)特大地震影響船侧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜堕汞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一勺爱、第九天 我趴在偏房一處隱蔽的房頂上張望晃琳。 院中可真熱鬧讯检,春花似錦、人聲如沸卫旱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽顾翼。三九已至投放,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間适贸,已是汗流浹背灸芳。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拜姿,地道東北人烙样。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蕊肥,于是被迫代替她去往敵國(guó)和親谒获。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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

  • 模式轉(zhuǎn)換## 常用的兩種模式就是普通模式和插入模式Normal Mode進(jìn)入Insertion Mode dele...
    AwesomeAshe閱讀 813評(píng)論 0 3
  • What an ironic story...There is no lack of examples of su...
    JamP閱讀 220評(píng)論 0 0
  • 藍(lán)天 白云 一望無垠的沙漠 河流 胡楊 流連忘返的姑娘 紅衫 鋼鐵 巍峨挺立的機(jī)械 一片天下 兩個(gè)世界
    倚欄賞景閱讀 142評(píng)論 0 0
  • 領(lǐng)導(dǎo)力是人與人之間的過程壁却,在這個(gè)過程中批狱,任何決定都是由領(lǐng)導(dǎo)者與追隨者共同決定的。 外向性格可以做領(lǐng)導(dǎo)者展东,內(nèi)向性格同...
    七年新生閱讀 215評(píng)論 0 0