基本情况:在React项目中一个表格的数据是由A接口和B接口这两个接口的数据拼合而成,且其中表格列colD的数据是从B接口获取的,而B接口的调用需要拿A接口中获取的每一条数据的id进行请求。
方法一:
一般遇到这种问题最先想到的就是先调用A接口,再遍历A接口的数据一条一条的循环请求B接口,将数据拼在一起后再渲染表格数据。
// 表格数据
const [anDataSource, setDataSource] = useState([]);
// 获取表格数据函数
const doSearch = async () => {
// 分页查询的参数
const params = {
pageNum: 1,
pagesize: 10,
... //其它请求参数
}
// 先调用A接口
const tempData = await getA(params);
// 接口返回的数据格式大致如下:
/*
{
code: "0",
list: [
{
id: 1,
colA: '数据1',
colB: '数据1',
colC: '数据1',
},
{
id: 2,
colA: '数据2',
colB: '数据2',
colC: '数据2',
},
...
],
total: 10,
success: true
}
*/
const promiseAll = tempData.list?.map((item) => {
return getB({ id: item.id }).then(({ data }) => ({ ...item, colD: data }));
});
const resultData = await Promise.allSettled(promiseAll);
tempData.list = resultData.map((item) => item.value);
// 获取最终的表格数据
setDataSource(tempData.list);
}
<Table dataSource={anDataSource} hasBorder={false} primaryKey="id" ></Table>
方法一中每次查询数据都需要循环调用接口,等所有接口数据返回后再渲染表格,这在一定程度上加长了表格loading的显示时长,对于用户的体验感不是很好。并且当用户短时间内连续多次点击“下一页”按钮时有多个请求处于pending待响应状态,容易出现接口请求超时的情况。
方法二
可以先渲染从A接口拿到的数据,等用户在此页面停留3秒后再调用B接口,等全部接口请求完成后重新渲染表格数据。
我们可以设置一个isSearchFlag用于标识是否处于请求状态,并用currentTableData存储当前表格数据。
const {
currentTableData,
} = state;
// 获取表格数据函数
const doSearch = async () => {
isSearchFlag = false;
const params = {
pageNum: 1,
pagesize: 10,
... //其它请求参数
}
const tempData = await getA(params);
setState({
currentTableData: tempData.list
});
isSearchFlag = true;
}
useEffect(() => {
if (isSearchFlag) {
setTimeout(() => {
console.log('Searching...');
async function fetchData() {
const promiseAll = currentTableData?.map((item) => {
return getB({ id: item.id }).then(({ data }) => ({ ...item, colD: data }));
});
const resultData = await Promise.allSettled(promiseAll);
let tempArray = currentTableData;
tempArray = resultData.map((item) => item.value);
setState({
currentTableData: tempArray
});
}
fetchData();
}, 3000); // 3秒
}
}, [isSearchFlag]);
<Table dataSource={currentTableData} hasBorder={false} primaryKey="id" ></Table>
方法三
在路由拦截器中设置请求计数器requestCountMap,设置接口请求次数限制为20,当请求区的计数达到限制次数时拦截此后的请求,只有当有请求被响应后再允许发起新的请求。
const request = axios.create({
baseURL: baseURL,
// 可选的,全局设置 request 是否返回 response 对象,默认为 false
// withCredentials: true,
});
// 存储请求计数的Map
const requestCountMap = new Map();
// 添加请求拦截器
request.interceptors.request.use(config=>{
// config 请求配置
if (config.url.indexOf('B接口地址') > -1) {
// 获取当前URL的计数
let count = requestCountMap.get(config.url) || 0;
// 如果计数超过20,抛出错误阻止请求
if (count > 20) {
throw new Error('Request limit exceeded.');
}
// 更新计数
requestCountMap.set(config.url, ++count);
}
// 可用于
// 发送网络请求时,在界面显示一个请求的同步动画
// 某些请求(比如登录(token))必须携带一些特殊的信息
// 请求成功拦截
console.log("请求拦截器")
return config
}, err => {
// 请求失败拦截
return Promise.reject(err)
})
// 添加响应拦截器
request.interceptors.response.use(res=>{
// res 响应结果
// 响应拦成功拦截
if (response.config.url.indexOf('B接口地址') > -1) {
// 请求成功处理
const config = response.config;
// 请求完成后将计数减少
let count = requestCountMap.get(config.url);
if (count > 0) {
requestCountMap.set(config.url, --count);
}
}
console.log("响应拦截器")
return res
}, err => {
// 响应拦失败拦截
if (err.config.url.indexOf('B接口地址') > -1) {
// 请求错误处理
const config = err.config;
// 请求完成后将计数减少
let count = requestCountMap.get(config.url);
if (count > 0) {
requestCountMap.set(config.url, --count);
}
}
return Promise.reject(err)
})
这种方法虽然拦截了部分请求,有效改善出现接口请求超时的问题,但是此方法拦截的是最新的请求,不符合用户想要看的是最新数据的需求,故不采用。
方法四
利用Promise模拟任务队列,从而实现请求池效果。
import axios from 'axios'
// 定义请求池主函数函数
export const handQueue = (
reqs // 请求总数
) => {
reqs = reqs || []
const requestQueue = (concurrency) => {
concurrency = concurrency || 6 // 最大并发数
const queue = [] // 请求池
let current = 0
// 定义dequeue函数
const dequeue = () => {
while (current < concurrency && queue.length) {
current++;
const requestPromiseFactory = queue.shift() // 出列
requestPromiseFactory()
.then(() => { // 成功的请求逻辑
})
.catch(error => { // 失败
console.log(error)
})
.finally(() => {
current--
dequeue()
});
}
}
// 定义返回请求入队函数
return (requestPromiseFactory) => {
queue.push(requestPromiseFactory) // 入队
dequeue()
}
}
// 试验
const enqueue = requestQueue(6)
for (let i = 0; i < reqs.length; i++) {
enqueue(() => axios.get('/api/test' + i))
}
}
只有有请求响应成功的同时才会有新的请求进来,极大的降低里服务器的压力。
方法四参考文档地址