效果:
思路:
首先axios 支持AbortController功能,创建一个AbortController实例,
const controller = AbortController;
并将实例对象的signal作为axios请求参数
const signal = controller.signal;
axios.request({signal});
如果请求时间很长,调用abort方法可以终止请求
controller.abort();
基于此原理,我们可以为每一个请求生成唯一的请求标识requestId,并使用一个map存储requestId和该请求AbortController的映射关系,当我们发送请求时,判断map是否存在该请求的id,如果存在则调用该请求AbortController的终止方法,并重新生成一个AbortController.
如果请求返回,不论是否成功,都需要清除map的requestId,否则可能会造成内存泄漏,因为组件卸载后重新挂载会生成新的请求id,之前的请求id则一直在map中被引用。
export const requestIdMap = new Map<symbol, AbortController>(); //存放请求映射放系
export const apiRequest = async <T>({ url, requestId, ...config }: ApiProps): Promise<T> => { const controll = requestIdMap.get(requestId); try { //如果map包含请求的requestId,说明请求还没有响应,需要关闭改请求 if (controll) { controll.abort(); requestIdMap.delete(requestId); } //如果请求没有包含requestId,请求可以发送,并把请求关系存储map requestIdMap.set(requestId, new AbortController()); const res = await axios.request({ url, ...config, signal: requestIdMap.get(requestId)?.signal, }); //请求结束之后需要将请求的id从map删除 requestIdMap.delete(requestId); return Promise.resolve(res.data); } catch (error) { if (error instanceof CanceledError) { if (error.message !== 'canceled') { //请求结束之后需要将请求的id从map删除 requestIdMap.delete(requestId); } } return Promise.reject(error); }};
那么如何为每一个request请求创建唯一的标识呢? 可以封装一个react hooks,该hooks生成一个symbol对象作为唯一标识,并且在组件卸载的时候,防止有请求没有响应,但是组件已经卸载,然后出现经典的react 报错,无法对已经卸载的组件执行状态更新.
export const useRequestId = () => { const refRequestId = useRef(Symbol()); useEffect(() => { const requestId = refRequestId.current; return () => { const controller = requestIdMap.get(requestId); controller?.abort(); }; }, [refRequestId]); return refRequestId.current;};
最后使用axios的时候只需要在请求参数添加requestId即可.
cosnt requestId = useRequestId();
apiRequest({url,requestId});