不到50行程式碼實現一個能對請求併發數做限制的通用RequestDecorator

陳紀庚發表於2018-09-13

使用場景

在開發中,我們可能會遇到一些對非同步請求數做併發量限制的場景,比如說微信小程式的request併發最多為5個,又或者我們需要做一些批量處理的工作,可是我們又不想同時對伺服器發出太多請求(可能會對伺服器造成比較大的壓力)。這個時候我們就可以對請求併發數進行限制,並且使用排隊機制讓請求有序的傳送出去。

介紹

那麼,接下來我們就來講一下如何實現一個通用的能對請求併發數進行限制的RequestDecorator。我們先來介紹一下它的功能:

  1. 既然涉及到併發數限制,它就肯定允許使用者傳入最大併發數限制引數:maxLimit
  2. 既然是一個通用的RequestDecorator,那麼它應該允許使用者傳入其喜歡的非同步api(比如ajax, fetch, axios等)。
  3. 為了方便起見,也為了開發便利性,被RequestDecorator封裝後的request請求結果都返回一個promise。
  4. 由於使用者傳入的非同步api不一定是promise型別的,也可能是callback型別的,因此我們提供使用者一個needChange2Promise引數,使用者若傳入的是callback型別的api,它可以通過將這個引數設定為true來將callback型別轉化為promise型別。

分析完功能後,接下來我們就來實現這個東西:

實現

具體程式碼如下,每一步我基本都做了註釋,相信大家能看懂。

const pify = require('pify');

class RequestDecorator {
  constructor ({
    maxLimit = 5,
    requestApi,
    needChange2Promise,
  }) {
    // 最大併發量
    this.maxLimit = maxLimit;
    // 請求佇列,若當前請求併發量已經超過maxLimit,則將該請求加入到請求佇列中
    this.requestQueue = [];
    // 當前併發量數目
    this.currentConcurrent = 0;
    // 使用者定義的請求api,若使用者傳入needChange2Promise為true,則將使用者的callback類api使用pify這個庫將其轉化為promise類的。
    this.requestApi = needChange2Promise ? pify(requestApi) : requestApi;
  }
  // 發起請求api
  async request(...args) {
    // 若當前請求數併發量超過最大併發量限制,則將其阻斷在這裡。
    // startBlocking會返回一個promise,並將該promise的resolve函式放在this.requestQueue佇列裡。這樣的話,除非這個promise被resolve,否則不會繼續向下執行。
    // 當之前發出的請求結果回來/請求失敗的時候,則將當前併發量-1,並且呼叫this.next函式執行佇列中的請求
    // 當呼叫next函式的時候,會從this.requestQueue佇列裡取出隊首的resolve函式並且執行。這樣,對應的請求則可以繼續向下執行。
    if (this.currentConcurrent >= this.maxLimit) {
      await this.startBlocking();
    }
    try {
      this.currentConcurrent++;
      const result = await this.requestApi(...args);
      return Promise.resolve(result);
    } catch (err) {
      return Promise.reject(err);
    } finally {
      console.log('當前併發數:', this.currentConcurrent);
      this.currentConcurrent--;
      this.next();
    }
  }
  // 新建一個promise,並且將該reolsve函式放入到requestQueue佇列裡。
  // 當呼叫next函式的時候,會從佇列裡取出一個resolve函式並執行。
  startBlocking() {
    let _resolve;
    let promise2 = new Promise((resolve, reject) => _resolve = resolve);
    this.requestQueue.push(_resolve);
    return promise2;
  }
  // 從請求佇列裡取出隊首的resolve並執行。
  next() {
    if (this.requestQueue.length <= 0) return;
    const _resolve = this.requestQueue.shift();
    _resolve();
  }
}

module.exports = RequestDecorator;
複製程式碼

樣例程式碼如下:

const RequestDecorator = require('../src/index.js')

// 一個callback型別的請求api
function delay(num, time, cb) {
  setTimeout(() => {
    cb(null, num);
  }, time);
}

// 通過maxLimit設定併發量限制,needChange2Promise將callback型別的請求api轉化為promise型別的。
const requestInstance = new RequestDecorator({
  maxLimit: 5,
  requestApi: delay,
  needChange2Promise: true,
});


let promises = [];
for (let i = 0; i < 30; i++) {
  // 接下來你就可以像原來使用你的api那樣使用它,引數和原來的是一樣的
  promises.push(requestInstance.request(i, Math.random() * 3000).then(result => console.log('result', result), error => console.log(error)));
}
async function test() {
  await Promise.all(promises);
}

test();
複製程式碼

這樣,一個能對請求併發數做限制的通用RequestDecorator就已經實現了。當然,這裡還有很多可以繼續增加的功能點,比如

  1. 允許使用者設定每個請求的retry次數。
  2. 允許使用者對每個請求設定快取處理。

優點:

  1. 不修改使用者原來的request api程式碼。對原有程式碼無副作用。
  2. 不修改request api的呼叫方式。使用者可以無縫的使用被RequestDecorator封裝過的request。
  3. 可擴充套件,後續可能不止支援併發量限制,還可能增加快取、retry等額外的功能。

結語

以上,就是本篇的全部內容。github倉庫地址點選這裡。歡迎大家點贊或者star下。如果大家有興趣的話,也可以一起來完善這個東西。這個專案還不成熟,可能還會有bug,歡迎大家在github上提issue幫助我完善它。如果覺得有幫助的話,麻煩點個贊哦,謝謝。 本文地址在->本人部落格地址, 歡迎給個 start 或 follow

相關文章