背景

对于后台系统,重复提交是一个特别常见的问题,目前我们的做法是,添加loading,或者是临时根据一个变量来判断是否可以提交,这种方法没有问题,缺点就是需要每次都声明变量,并且容易被开发者忘记,所以我们在想,是否可以添加一个全局的axios拦截,将重复的请求都去掉?

接口取消请求

axios文档中其实由一个专门用于取消请求的工厂方法CancelToken.source,它是基于proposal-cancelable-promises的,需要注意的是,它目前还处于第一阶段,使用方法如下:

  1. const CancelToken = axios.CancelToken;
  2. const source = CancelToken.source();
  3. axios.get('/user/12345', {
  4. cancelToken: source.token
  5. }).catch(function(thrown) {
  6. if (axios.isCancel(thrown)) {
  7. console.log('Request canceled', thrown.message);
  8. } else {
  9. // 处理错误
  10. }
  11. });
  12. axios.post('/user/12345', {
  13. name: 'new name'
  14. }, {
  15. cancelToken: source.token
  16. })
  17. // 取消请求(message 参数是可选的)
  18. source.cancel('Operation canceled by the user.');

CancelToken.source方法会返回一个对象source,在需要取消的axios请求配置中加入cancelToken:source.token,调用source.cancel()方法即可取消掉该请求

全局配置

在全局维护一个队列,在每次请求的同时,判断一下该队列中是否已经存在相同的请求,如果有,则将当前请求取消掉,如果没有,则正常请求,并且加入到pending队列中,当请求结束后,将该请求从队列中删除即可

注意:
1. 对一个请求是否重复的判断,需要你根据项目的实际情况来判断,可能有的接口是相同名字但是参数不同,可能有的请求是前端需要进行循环请求的,它们就都是可以进行重复请求的,所以对于重复的定义,请自行斟酌!
2. cancelToken基于proposal-cancelable-promises,它还处在第一阶段
  1. // axios.js
  2. let pendingQueue = new Map()
  3. // 取消需要从全局的axios中拿,如果你用了create,也需要拿导入的axios(随便你命名)
  4. let CancelToken = axios.CancelToken;
  5. // 拦截
  6. axios.interceptors.request.use((config) => {
  7. // 可能需要根据传参来判断是否需要进行重复请求校验
  8. // if (config.data && !config.data.GLOBAL_CANCEL) {
  9. // let flag = judgePendingFuncNew(config);
  10. // // 将pending队列中的请求设置为当前
  11. // let source = CancelToken.source();
  12. // config.cancelToken = source.token;
  13. // if (flag) {
  14. // // 当前的请求是重复的
  15. // source.cancel('取消请求');
  16. // } else {
  17. // // 当前请求是一个新请求
  18. // pendingQueue.set(`${config.method}->${config.url}`, true);
  19. // }
  20. // }
  21. // 请求发起之前先检验该请求是否在队列中,如果在就把队列中pending的请求cancel掉
  22. // 此处还可以添加全局的loading,但是需要记住在response中关掉loading,同时在请求失败的catch中也关闭loading
  23. let flag = judgePendingFuncNew(config);
  24. // 将pending队列中的请求设置为当前
  25. let source = CancelToken.source();
  26. config.cancelToken = source.token;
  27. if (flag) {
  28. // 当前的请求是重复的
  29. source.cancel('取消请求');
  30. } else {
  31. // 当前请求是一个新请求
  32. pendingQueue.set(`${config.method}->${config.url}`, true);
  33. }
  34. // ...
  35. })
  36. // 响应
  37. axios.interceptors.response.use(response => {
  38. removeResolvedFunc(response.config)
  39. // ...
  40. )}
  41. const judgePendingFuncNew = function(config) {
  42. if (pendingQueue.has(`${config.method}->${config.url}`)) {
  43. return true;
  44. }
  45. return false;
  46. }
  47. // 删除队列中对应已执行的请求
  48. const removeResolvedFunc = function (config) {
  49. if (pendingQueue.has(`${config.method}->${config.url}`)) {
  50. pendingQueue.delete(`${config.method}->${config.url}`)
  51. }
  52. }

注意:取消需要从全局的axios中拿,如果你用了create,也需要拿导入的axios(随便你命名)如下:

  1. // 修改了导入名称
  2. import axiosApi from 'axios'
  3. window.axios = axiosApi;
  4. let pendingQueue = new Map()
  5. // 用原本导入的axiosApi而不是后面的axios
  6. let CancelToken = axiosApi.CancelToken;
  7. // 响应时间
  8. let axios = axiosApi.create({
  9. baseURL: ''
  10. })

番外

取消接口请求还可以运用到没有权限的情况下,是否有权限,都是通过接口判断的,如果一个页面没有权限,但是请求了多个接口,则会多个无权限提示弹窗,这种情况下,可以把后面的接口取消掉

  1. // axios.js
  2. window.axios = axiosApi;
  3. let source = axiosApi.CancelToken.source();
  4. axios.interceptors.request.use((config) => {
  5. ...
  6. if (!config.cancelToken) {
  7. config.cancelToken = source.token;
  8. }
  9. ...
  10. });
  11. axios.interceptors.response.use(response => {
  12. ...
  13. if (response.data.code === 1107) {
  14. source.cancel();
  15. // 重置token
  16. source = axiosApi.CancelToken.source();
  17. throw new Error(data.data.msg)
  18. ...
  19. })