使用Axios AbortController取消上一次尚未返回的重复请求

1,322 阅读2分钟

效果:

思路: 

首先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});