js实现并发请求控制
批量请求
先想想如何批量请求?
这里创建一个模拟接口的方法供后续示例使用:
js
function simulateApi(res, delay = 500) {
return new Promise((resolve, reject) => {
setTimeout(res ? resolve : reject, delay, res || new Error('Manual error'))
})
}
for...of
js
async function processItemsSequentially() {
const result = []
for (const id of [1, 2, 3, 4, 5]) {
const data = await simulateApi(id)
result.push(data)
}
console.log(result) // [1, 2, 3, 4, 5]
}
processItemsSequentially()
上例是使用for...of实现的顺序请求。
Promise.all
使用数组的 map
方法无法返回异步结果,但 Promise.all 可以:
js
let result = []
Promise.all([1, 2, 3, 4, 5].map(id => simulateApi(id)))
.then(res => result = res)
.catch(err => console.error(err))
Promise.allSettled 也可以用于批量请求,其异同这里不再赘述。
控制并发请求
某些场景下,我们需要控制页面内并发请求数,而不是一股脑的抛给浏览器
工具库
相关库:
自行实现
思路:
- 设置并发上限,记录正在请求的数量
- 当请求数量未达上限时,直接发起请求,更新计数
- 有请求完成时,更新计数
- 当请求数量达到上限时,等待请求完成再发起新的请求
简单来说,就是一个排队等柜台叫号的过程,柜台数也就是并发上限。🤣
以下,仅供参考
源码:
js
export function concurrencyRequest(limit = 5) {
const tasks = [] // 待请求队列、任务列表
const queue = [] // 正在请求的队列
let qId = 0 // 请求id标识
let qIndex = -1 // 待请求索引
/**
* 封装请求函数
*
* @param {Function} fn 源异步请求函数
* @returns {Promise} 返回一个应用并发控制后的promise
*/
function limitedReqWrap(fn, ...args) {
const { promise, resolve, reject } = Promise.withResolvers()
tasks.push({ id: qId++, pending: true, fn, args, resolve, reject })
checkQueue()
return promise
}
// 叫号:检查请求队列,从任务列表中添加新的请求
function checkQueue() {
if (queue.length >= limit) return // 请求队列已满
if (!tasks[qIndex + 1]) return // 任务列表已空
// 从任务列表中添加请求
queue.push(tasks[++qIndex])
execReqsInQueue()
}
// 执行请求队列中未开始的请求
function execReqsInQueue() {
for (const req of queue) {
if (!req.pending) continue
req.pending = false
const { id, fn, args, resolve, reject } = req
fn(...args)
.then(resolve, reject)
.finally(() => handleRequestSettled(id))
}
}
// 请求完成。从正在请求队列中移除,尝试添加下一个请求
function handleRequestSettled(currId) {
const index = queue.findIndex(p => p.id === currId)
if (index > -1) {
queue.splice(index, 1)
checkQueue()
}
}
// 清空待请求队列(tasks)
function clearTasks() {
tasks.length = 0
qIndex = -1
// 如果支持,可以在这里中断已发起的异步请求(queue)
}
return { limitedReqWrap, clearTasks }
}
/**
* 模拟接口
*
* @param {*} res 返回值
* @param {number} delay 延迟时间
*/
export function simulateApi(res, delay = 500) {
return new Promise((resolve, reject) => {
setTimeout(res ? resolve : reject, delay, res || new Error('Manual error'))
})
}
vue
<script setup>
import { ref } from 'vue'
import { simulateApi, concurrencyRequest } from './utils'
const requestList = ref([])
let requestId = 0
const { limitedReqWrap, clearTasks } = concurrencyRequest()
// 添加异步请求
function addRequest() {
const currReq = { status: 'pending', id: requestId++, res: null, err: null }
const delay = Math.floor(Math.random() * 500 + 100)
requestList.value.push(currReq)
// 模拟请求,延时100ms~600ms响应,小概率失败
limitedReqWrap(simulateApi, delay < 160 ? false : delay, delay)
.then((val) => {
const target = requestList.value.find(v => v.id === currReq.id)
// console.log('success ', currReq.id)
if (!target) return
target.status = 'resolved'
target.res = val
})
.catch((err) => {
const target = requestList.value.find(v => v.id === currReq.id)
// console.log('failed: ', currReq.id)
if (!target) return
target.status = 'rejected'
target.err = err
})
}
function add20Requests() {
for (let i = 0; i < 20; i++) addRequest()
}
function clear() {
requestList.value = []
clearTasks()
}
</script>
<template>
<!-- <h1>测试:并发请求控制</h1> -->
<button @click="addRequest">点击添加异步请求</button>
<button @click="add20Requests">点击添加二十个异步请求</button>
<button @click="clear">清空</button>
<hr />
<ol>
<li
v-for="item in requestList"
:key="item.id"
:style="{ color: { pending: 'orange', resolved: 'green', rejected: 'red' }[item.status] }"
>
Status: {{ item.status }}
<template v-if="item.status !== 'pending'">
(
<span v-if="item.res">Result: {{ item.res || '' }}</span>
<span v-if="item.err">Error: {{ item.err?.message || item.err || '' }}</span>
)
</template>
</li>
</ol>
</template>
Last updated: