前端在发送请求之后,在等待请求返回的时候处于空闲状态,仅对这个请求来讲,不需要处理任何事情。将处理请求结果的函数放到微任务队列里面,等请求返回之后再进行处理,就可以将这段时间释放,去做其他事情。使用这种方式发送多个请求,就可以实现并发的效果。
如果一个页面内的请求数量过多,请求的规模变大,就需要建立一个管理请求的队列,统一管理请求的发送和处理。目前主流的处理方案类似下面的代码:
function fetchQueue(urls: Promise<any>[], maxNum: number) {
return new Promise((resolve, reject) => {
if (urls.length === 0) {
resolve([])
return
}
const result = new Array(urls.length)
let index = 0
const request = async () => {
const i = index
const url = urls[index]
index++
try {
const data = await url
result[i] = data
} catch (e) {
result[i] = e
} finally {
if (index < urls.length) {
request()
} else {
console.log(result)
resolve(result)
}
}
}
for (let i = 0; i < Math.min(maxNum, urls.length); i++) {
request()
}
})
}
上面的代码传入一个 Promise 数组 urls,在 request 函数中通过索引 index 取出队列里的 promise,然后自增 index,以 await 的方式等待取出的 promise 执行完成,最后在 finally 内部再次调用 request 函数,进行链式调用,不断的从队列中取出 promise,然后执行。
通过在 promise 的 finally 里面调用函数自身也可以达到这种效果,这里将其称为一个请求流程,该请求流程同步执行。fetchQueue 中通过 for 循环开启了多个这样的请求流程,可以达到并发的效果,并且控制最大请求数,避免过高的内存和性能占用。
如果队列里的请求没有得到补充,在将队列里的请求消耗完成之后,通过 for 循环开启的这些流程就会停止执行,不会再次开启。这时候可以为每一个流程分配一个状态,通过轮询这些流程的状态,在流程结束之后再次开启,这样就能应对需要发送大量连续请求的场景。伪代码如下:
const concurrency = 6
const state = new Array(concurrency).fill(false)
const urls = []
const request = (i: number) => {
if (urls.length > 0) {
state[i] = true
...
finally(() => {
request(i)
})
...
} else {
state[i] = false
}
}
setInterval(() => {
for (let i = 0; i < concurrency; i++) {
if (!state[i]) {
request(i)
}
}
}, 500)
上面的代码使用 state 存储各流程的状态,初始为 false,表示流程没有开启。通过定时器每 500 毫秒检查一次各流程的状态,检测到流程关闭或者未开启之后尝试开启此流程,再然后通过队列里是否存在元素设置 state 的状态。
为了找到最合适的并发数,可以结合平均请求时间、每秒钟入队的请求数来计算,通过平均请求时间来计算出每秒钟可以完成请求的平均数量,将每秒入队的请求数除以每秒能完成请求的平均数量,就可以得到最合适大小的并发数。
mlfetch 是本人针对需要发送大量连续请求的场景开发的请求队列管理库,包括了上面介绍的所有功能点,项目还在完善阶段,欢迎在 github
上开个 issue
,提出建议。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。