实现一个简单的并发请求队列

前言

最近维护一个老项目中的微信公众号h5的新需求,项目是Node.js服务,Node层负责前端路由、api聚合以及用户信息校验等工作,项目较大,上一次维护已经是3年前,现在不方便重构整个项目,新需求也依赖Node层做路由和api管理,因此需要在原项目内做开发。

原项目的前端资源由fis3输出到指定目录,其中js、css、img资源会上传至cdn,由另一台服务器nginx代理(文件上传服务会由另一个Node服务处理),Node层只负责解析html的解析,以减少Node层业务的压力。新需求基于Vue开发,需要webpack打包,因此需要通过其他方法上传静态资源到nginx的服务器。

文件上传请求是一个异步操作,那么是否可以通过并发请求,加快上传的速度呢?新需求是多页面,资源有上百个文件,同时并发上百个请求会对服务器造成过大压力,我们需要一定的机制让请求排队,除此以外,当上传失败时,还需要一定的重试能力。面对这些问题,接下来研究一下如何实现上述的需求。

尝试与学习

这里我参考了一篇并发请求的实践,可以先了解一下原文:

不到50行代码实现一个能对请求并发数做限制的通用RequestDecorator - 作者:陈纪庚

在大佬的基础上做了一些简单的改造,增加了重试的功能,同时项目开发中也遇到了一些小问题,以下是具体的实现,有注释说明:

// 任务队列
class RequestQueue {
  constructor(maxLimit = 5, retry = 2) {
    // 最大并发量
    this.maxLimit = maxLimit
    // 重试次数
    this.retry = retry

    // blocking queue 若当前请求并发量已经超过maxLimit,则将请求延迟到下某个任务完成,再执行该队列任务
    this.requestQueue = []
    // 当前并发量数目
    this.currentConcurrent = 0
    
    // 说明1:
    // 实际请求中,可能会异步的抛出多个error
    // 任务重试过程中,当catch到 error且 重试已到上限,会执行 next() 执行下一个任务,
    // 此时,如果有异常抛出前一个异步任务的,会无法捕获 
    // 因此通过全局时间捕获剩余的异步异常
    process.on("unhandledRejection", function(e){
      console.log(e);
    })
  }

  async run(request) {
    // 并发限制
    if (this.currentConcurrent >= this.maxLimit) {
      await this.startBlocking() // 等待执行,直到某个任务执行this.next()
    }
    // 队列+1
    this.currentConcurrent++

    // 设置队列中同一个任务尝试次数
    for (let retryCount = this.retry; retryCount > 0; retryCount--) {
      let done = false
      console.log('[ retryCount ]:' + retryCount)
      try {
        // 这里与大佬的方法有不同,这里需要传入一个包装好请求的Promise实例,如有需要也可以用pify将请求转成promise
        const result = await request() 
        // 执行成功则结束尝试
        done = true
        return Promise.resolve(result)
        // 如果有错误,会被捕获,不会执行resolve
      } catch (error) {
        console.log('[ request error ] - ' + error)
        // 最后一次重试失败时停止重试,返回报错
        if (retryCount === 1) {
          done = true
          return Promise.reject(error) // 错误只会抛出一次
        }
      } finally {
        // 如果已经结束重试,执行请求队列的下一个任务
        if (done) {
          this.currentConcurrent--
          this.next()
          break;
        }
      }
    }
  }

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

  startBlocking() {
    let _resolve
    let promise2 = new Promise((resolve) => (_resolve = resolve))
    this.requestQueue.push(_resolve)
    return promise2 // 返回block promise 用于暂停队列的执行
  }
}

使用方式:

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)取决于是否允许某个任务失败时,其他任务继续执行
       console.log(err)
    })
  )
}

Promise.all(promises)
  .catch(err => console.log(err)) 
  // 如果前面的push过程中不catch,则一旦有任务抛出错误,剩余的任务不再执行

整个实现如上,与大佬的实践略有不同,仅供学习。实际生产中,更推荐使用开源社区成熟的库,async,这个库提供更全面的异步流控制,便于我们进行开发。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,858评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,372评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,282评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,842评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,857评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,679评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,406评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,311评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,767评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,945评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,090评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,785评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,420评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,988评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,101评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,298评论 3 372
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,033评论 2 355

推荐阅读更多精彩内容