Promise控制并发请求数量

6/13/2021 Promise并发请求

# Promise 控制并发请求数量

前言:

浏览器对对同一个服务器的并发数是有限制的,参考如下表格(表格来源于网络,未进过严谨测试):

浏览器 HTTP / 1.1 HTTP / 1.0
IE 11 6 6
IE 10 6 6
IE 9 10 10
IE 8 6 6
IE 6,7 2 4
火狐 6 6
Safari 3,4 4 4
Chrome 4+ 6 6
歌剧 9.63,10.00alpha 4 4
Opera 10.51+ 8
iPhone 2 4
iPhone 3 6
iPhone 4 4
iPhone 5 6
Android2-4 4

基于这个问题,加上多图上传的功能,就会出现 bug 了!!

如果接口请求超时时间为 5s,用户同时选择了 20-30 张图,每张图上传时间平均为 1s,并且图片用了分片上传

那就回得到,并发请求约等于 20,那么后面的请求就会被阻塞(注意只是阻塞,不过接口也在算时间了)。加上分片上传的逻辑,一张图片又被切为 3-4 个分片,再次分出 3-4 个请求~ 芜湖起飞

如果接口响应的慢,后面被阻塞的接口超过 5s 就就会被关闭,图片上传就被打断了,所以并发的请求不能给浏览器去帮我们挂起,而是要让我们自己控制

# 方案 1- async 和 await

这是最优雅的方案,代码量也少,上传图片只会上传完一张后在进行下一张,伪代码如下:

function uploadFn() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({ success: 11 })
    }, 2000)
  })
}

async function upload() {
  var _upload = uploadFn
  let array = [1, 2, 3, 4, 5, 6, 7, 8, 9]
  for (let index = 0; index < array.length; index++) {
    const element = array[index]
    console.log('循环开始')
    let res = await _upload.call(this)
    console.log(res, 'await结束')
  }
}

upload()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

如果按上面的方式一次性拿到多张图,那倒问题不大,可是如果组件是重复调用,await 就控制不住了

upload()
upload()
upload()
1
2
3

所以还得从根源解决问题

# limit-promise 并发请求控制

我也是看到一位大佬的博客,贴上他的 GitHub 仓库limit-promise (opens new window)

核心代码不长:

const LimitPromise = function(max) {
  this._max = max
  this._count = 0
  this._taskQueue = []
}

LimitPromise.prototype.call = function(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()
    }
  })
}

LimitPromise.prototype._createTask = function(caller, args, resolve, reject) {
  return () => {
    caller(...args)
      .then(resolve)
      .catch(reject)
      .finally(() => {
        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)
  }
}

if (typeof Promise.prototype.finally !== 'function') {
  Promise.prototype.finally = function(callback) {
    let P = this.constructor
    return this.then(
      value => P.resolve(callback()).then(() => value),
      reason =>
        P.resolve(callback()).then(() => {
          throw reason
        })
    )
  }
}

module.exports = LimitPromise
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

使用的时候

const LimitPromise = require('limit-promise')

// 引入自己写的请求的方法
const request = require('./request')

const MAX = 5

// 设置并发请求数量
const limitP = new LimitPromise(MAX)

// 重新封装一下请求的方法,最后都用 limitP 去发请求
function get(url, params) {
  return limitP.call(request.get, url, params)
}

function post(url, params) {
  return limitP.call(request.post, url, params)
}

module.exports = { get, post }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Last Updated: 1/7/2024, 5:51:59 PM