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()
如果按上面的方式一次性拿到多张图,那倒问题不大,可是如果组件是重复调用,await 就控制不住了
upload()
upload()
upload()
所以还得从根源解决问题
limit-promise 并发请求控制
我也是看到一位大佬的博客,贴上他的 GitHub 仓库limit-promise
核心代码不长:
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
使用的时候
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 }