React Vite中使用Axios解决ConcurrencyViolation错误的方法
429请求过多错误(ConcurrencyViolation)解决方案
问题核心
你遇到的429 (Too Many Requests)错误,对应ConcurrencyViolation错误码和Max concurrency allowed 1 per 1 seconds提示,意思是接口限制每秒只能发起1个并发请求。你之前的尝试没解决问题,核心原因在于:
- 多个
useFetch钩子实例各自为政,没共享全局的限流规则 - 手动
sleep完全无效——多个请求的等待是并行执行的,到时间后还是会同时发起请求 - 之前用
p-limit时,每个钩子都创建了独立的限制器,等于没做全局限流
可行解决方案
1. 全局共享请求限制器
用p-limit必须保证所有请求都使用同一个限制实例,这样才能实现全局的1次/秒并发控制:
第一步:创建全局限制器文件
新建requestLimiter.js:
import pLimit from 'p-limit'; // 严格匹配接口要求:每秒最多1个请求 const requestLimit = pLimit(1); export default requestLimit;
第二步:修改useFetch钩子
把请求逻辑用全局限制器包裹,同时移除无效的sleep代码:
import requestLimit from './requestLimiter'; import axios from 'axios'; import { useEffect, useRef, useState } from 'react'; export default function useFetch(url, options) { const [news, setNews] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const isMounted = useRef(true); useEffect(() => { setLoading(true); // 用全局限制器包裹请求逻辑 const fetchTask = requestLimit(async () => { if (!isMounted.current) return; try { const res = await axios.request({ ...options, url }); if (isMounted.current) { setNews(res.data.articles); } } catch (err) { if (!isMounted.current) return; if (err.response) { setError(err.response.data); } else if (err.request) { console.log(err.request); } else { console.log(`Error: ${err.message}`); } } finally { if (isMounted.current) { setLoading(false); } } }); fetchTask(); // 组件卸载时标记状态,避免更新已卸载的组件 return () => { isMounted.current = false; }; }, [url, options]); // 注意将options加入依赖数组 return { news, loading, error }; }
2. 额外优化:添加请求取消机制
为了避免组件卸载或url/options变化后,旧请求依然执行导致的无效请求,可以给axios添加取消令牌:
import requestLimit from './requestLimiter'; import axios from 'axios'; import { useEffect, useRef, useState } from 'react'; export default function useFetch(url, options) { const [news, setNews] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const isMounted = useRef(true); const cancelTokenSource = useRef(null); useEffect(() => { setLoading(true); // 创建取消令牌 cancelTokenSource.current = axios.CancelToken.source(); const fetchTask = requestLimit(async () => { if (!isMounted.current) return; try { const res = await axios.request({ ...options, url, cancelToken: cancelTokenSource.current.token }); if (isMounted.current) { setNews(res.data.articles); } } catch (err) { if (axios.isCancel(err)) { console.log('请求已取消:', err.message); return; } if (!isMounted.current) return; if (err.response) { setError(err.response.data); } else if (err.request) { console.log(err.request); } else { console.log(`Error: ${err.message}`); } } finally { if (isMounted.current) { setLoading(false); } } }); fetchTask(); return () => { isMounted.current = false; // 取消未完成的请求 cancelTokenSource.current?.cancel('因组件卸载或参数变化取消请求'); }; }, [url, options]); return { news, loading, error }; }
为什么之前的尝试失败?
p-limit如果每个钩子都单独创建限制器,每个钩子的请求都会在自己的队列里执行,多个钩子同时发起请求还是会突破并发限制,必须全局共享一个限制器xhr、cross-fetch只是替换了请求库,没解决并发限流的核心问题- 手动
sleep是每个请求独立等待,多个请求的等待过程是并行的,到时间后依然会同时发起请求,完全起不到限流作用
内容的提问来源于stack exchange,提问作者user
相关产品推荐
相关产品推荐

