axios异步时序处理方案(一)

1,172 阅读2分钟

1.场景

  • 站点管理代客下单计算运费,弱网环境下,多次调用计算运费接口,后一次请求比前几次请求响应快,拿到的运费值不是最新的,导致运费不正确。
  • 联想组件,多次调用接口响应时序问题。

2.解决方案

在发起后一次请求的时候,将上一次未完成的请求cancel掉。

3.官网例子

可以使用 CancelToken.source 工厂方法创建 cancel token,像这样:

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function(thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
     // 处理错误
  }
});

axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})

// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');

还可以通过传递一个 executor 函数到 CancelToken 的构造函数来创建 cancel token:

const CancelToken = axios.CancelToken;
let cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // executor 函数接收一个 cancel 函数作为参数
    cancel = c;
  })
});

// cancel the request
cancel();

那我们如何确保取消的是上一次请求,而不是本次请求呢。

4.实例

import React, { useRef } from 'react';
import axios from 'axios';

const Test = () => {
/**存放取消的请求方法*/
const ref = useRef<any>(null);

/**取消请求*/
const cancelQuest = () => {
   if (typeof ref.current === 'function') {
     ref.current('终止请求'); // 取消请求
   }
 };

 /** 请求方法 */
 const loadData = (value: string): Promise<any[]> => {
   return new Promise((resolve, reject) => {
     /**执行请求操作*/
     cancelQuest();
     axios.get('/waybill/xxx', {
       cancelToken: new axios.CancelToken((c) => {
         ref.current = c;
       }),
     })
       .then((res: any) => {
         if (res.code === 200) {
           const { list = [] } = res?.data;
           resolve(list);
         } else {
           resolve([]);
         }
       })
       .catch((e) => {
         if (axios.isCancel(e)) {
           console.log('请求取消', e.message);
         } else {
           reject(e);
         }
       });
   });
 };
 
return(<>xxxx</>)
}

5.如何在拦截器中配置

  • 设置一个列表pendingList,用于存储当前处于pending的请求
const pendingList = new Map();
  • 提供getFetchKey方法,用于生成各个请求的标识,当为GET请求时,因为只用于获取数据,因此只要当method和url都一致时,我们就可以认为这是同一请求,而其他请求则还需要加上请求的参数。
const getFetchKey = (config) => {
  const { useCancelToken } = config;
  //useCancelToken 用于配置该接口是否需要检测查复请求
  if (useCancelToken) {
    const { headers, url, data, method, params } = config;
    let token;

    if (method === 'GET') {
      token = [method, url].join('&');
    } else {
      token = [method, url, JSON.stringify(data), JSON.stringify(params)].join('&');
    }

    return token;
  }
};
  • 添加请求到pendingList
const addPending = (config) => {
  const fetchKey = getFetchKey(config);
  if (fetchKey) {
    config.cancelToken =
      config.cancelToken ||
      new axios.CancelToken((cancel) => {
        if (!pendingList.has(fetchKey)) {
          pendingList.set(fetchKey, cancel);
        }
      });
  }
};
  • 移除请求从pendingList
const removePending = (config) => {
  const fetchKey = getFetchKey(config);
  if (fetchKey) {
    if (pendingList.has(fetchKey)) {
      pendingList.delete(fetchKey);
    }
  }
};
  • 移除并取消该请求从pendingList
const cancelPending = (config) => {
  const fetchKey = getFetchKey(config);
  if (fetchKey) {
    if (pendingList.has(fetchKey)) {
      const cancel = pendingList.get(fetchKey);
      cancel(fetchKey);
      pendingList.delete(fetchKey);
    }
  }
};

  • 在拦截器中添加以上方法
axios.interceptors.request.use((config) => {
  //发送请求前首先检查该请求是否已经重复,重复则进行取消并移除
  cancelPending(config);
  //添加该请求到pendingList中
  addPending(config);
  return config;
});

axios.interceptors.response.use((response) => {
  const config = response.config;
  //请求完成后移除该请求
  removePending(config);
  return response;
});

  • 最后,因取消请求抛出的error我们不应该返回给用户,使用axios.isCancel()判断当前请求是否是主动取消的
  axios.(options).then(...)..catch((error) => {
    if (axios.isCancel(error)) {
      console.warn('repeated request: ' + error.message);
      return;
    }

    reject(error);
  });

6.效果 效果图

更详细的api文档请前往axios