示例场景
在前端场景中,经常会出现发起多次请求的场景,由于每次请求花费的时间都不一致,可能会出现数据错乱/新请求结果被旧请求结果覆盖的情况,如:
- 通过搜索框进行检索
- 如检索"123",可能参数为"1"/"123"时,会分别发出一次请求,当参数"1"的请求时间过长,就可能会导致我们将参数为"1"的请求结果作为参数为"123"的请求结果进行展示。
- 列表加载更多
- 首屏幕内容多的情况下可能需要同时请求page1/page2/page3的数据,需要多次触发加载更多将多页数据进行拼接展示,如请求结果处理顺序与发起顺序不一致,会导致内容排序出现问题。
- 异步处理url上的参数并发起请求
- 由于初始化时可能会基于默认参数先发起一次请求-request1,然后异步处理(
如在 react 中的 useEffect 中处理)url上的参数再次发起请求-request2,此时如果 request1 耗费的时间要比 requst2 多,就会导致 request2 的请求结果被 request1 的请求结果覆盖,与预期结果不符.
- 由于初始化时可能会基于默认参数先发起一次请求-request1,然后异步处理(
示例代码
const fetchFileList = async (params: IFileListParam) => {
console.log('input>>>', params.page)
const res = await queryFileList(params)
console.log('output<<<', params.page)
setData(origin=>origin.concat(res.data))
}
[1,2,3].forEach(page=>{ fetchFileList({page})})
以上代码,为了保证列表数据排序是正确的,setData这段代码需要有序执行,然而我们得到的日志是这样的:
可以看出请求完成后,先处理的page3的数据,再处理的page2的数据,这样便会导致页面上的数据展示排序出现问题,其原因是page3的请求比page2的请求先完成
解决方案
为此,我们可以封装一个AsyncSortedRequest,确保请求有序,不会因为请求耗费时间不同导致数据错乱.
interface IQueue {
status: 'pending' | 'finished'
resolve?: (val: any) => void
reject?: (val: any) => void
value?: any
}
/**
* 确保请求有序,不会因为请求耗费时间不同导致数据错乱
* @param fn - 请求函数
* @returns
*/
export function AsyncSortedRequest(fn: Function) {
// 创建队列以保存所有的请求
const queue: IQueue[] = []
// 检查队列状态并执行
const checkQueue = () => {
if (!queue.map(item => item.status).some(status => status === 'pending')) {
while (!!queue.length) {
const request = queue.shift()
request?.resolve && request.resolve(request.value)
request?.reject && request.reject(request.value)
}
}
}
return function (...args) {
// 请求占位
const index = queue.length
queue[index] = {
status: 'pending'
}
return new Promise((resolve, reject) => {
// 处理请求结果并保存
fn(...args).then(res => {
queue[index] = {
value: res,
status: 'finished',
resolve
}
}, err => {
queue[index] = {
value: err,
status: 'finished',
reject
}
}).finally(() => {
// 检查队列
checkQueue()
})
})
}
}
AsyncSortedRequest 有了,之后我们将queryFileList作为参数传入即可:
const SortedRequest = AsyncSortedRequest(queryFileList)
const fetchFileList = async (params: IFileListParam) => {
console.log('input>>>', params.page)
const res = await SortedRequest(params)
console.log('output<<<', params.page)
setData(origin=>origin.concat(res.data))
}
[1,2,3].forEach(page=>{ fetchFileList({page})})
得到正确的有序日志输出是: