在axios速读中,我们了解到axios请求的取消方法。

const CancelToken = axios.CancelToken;

//请求前拦截器
axios.interceptors.request.use(function(config) {
    //添加取消token
    const source = CancelToken.source();
    config.cancelToken = source.token;

    //取消请求
    source.cancel("取消请求");

    return config;
}, function(error) {
    // 对请求错误做些什么
    return Promise.reject(error);
});

但是在vue的项目中,取消请求是分很多种情况的,为此在掘金看了一篇关于axios取消请求的文章,非常有道理,我也是用自己的笔记记起来,内容可能大相径庭,有兴趣可以看看这篇文章:《axios切换路由取消指定请求与取消重复请求并存方案》

axios需要取消请求的一些情况

  1. 重复请求同一个api地址
  2. vue路由页切换,取消上一个路由还在发起或者等待返回的aixos请求
  3. 部分请求并不需要取消,比如全局性的api请求

基于以上三种情况,基本上涵盖了目前vue项目中遇到的一些情况。

如何实现 原理部分

其实也很简单易懂,我们在axios请求拦截器里面,使用一个数组来保存需要取消请求的cancel方法,但是为了识别是否为重复请求,我们需要一个key值去判断。

这个key值的组成:method+api地址

这样只要是同一个请求协议,请求同一个api地址,那么我们应该是取消上一个请求,发起本次请求

那么处理路由页切换,我们在路由守卫beforeEach中,拿到这个数组,然后遍历,依次出发cancel方法。

但是这样的话难免会取消到不是这个路由页的请求,因为我们一开始就是将所有的aixos请求取消方法都传入了数组中保存,遍历的时候,所有的请求都会被我们取消。

所以,我们要设置一个配置属性,当这个属性为false时,我们就不在遍历中取消这个请求。

具体实现方法我们下面直接放代码:

取消重复请求

import axios from "axios";

//创建自定义axios示例
let api = axios.create({});

//取消请求的数组
let cancelToken = [];

//请求前拦截
api.interceptors.request.use(config => {
  //获取请求协议+路径
  const requestUrl = `${config.method} ${config.url}`;
  //查找取消请求标识中是否存在相同的requestUrl
  const cancelIndex = cancelToken.findIndex(item => {
    return item.url === requestUrl;
  });

  //存在就说明重复了
  if (cancelIndex > -1) {
    //取消
    cancelToken[cancelIndex].cancel();
    //删除该标识
    cancelToken.splice(cancelIndex, 1);
  };

  //当前请求添加取消方法
  const CancelToken = axios.CancelToken;
  const source = CancelToken.source();
  config.cancelToken = source.token;
  //传入本地取消请求
  cancelToken.push({
    url: requestUrl,
    cancel: source.cancel,
  });

  return config;
}, error => Promise.reject(error))

这样每次axios发请求前都会判断有没有重复请求,有的话取消上一次,记录本次取消请求,再发出请求。

但是会有一个问题,比如,如果我这次请求已经完成了,用户再发一次请求获取数据, 相同的地址还是会被识别为重复请求,因为数据残留了,那个已完成的请求还在我们的cancelToken数组里面。

所以,我们需要在请求成功后删除cancelToken对应的记录。

成功后删除cancelToken中的记录

如果我们要在成功后判断是否已经在cancelToken存在了,我们用requestUrl来判别,在aixos中,他对xhr进行了封装,我们获取到的response 并不是原生的response 对象,在response 对象里面,有一个config属性,这个属性是贯穿请求前后请求后的,也就是说,我们在请求前对config做的设置,请求后依旧可以通过config获取到。

这样我们将requestUrl存放在config中,这样响应后,我们依旧知道,响应的这个请求是谁的请求。

于是乎:

import axios from "axios";

//创建自定义axios示例
let api = axios.create({});

//取消请求的数组
let cancelToken = [];

//请求前拦截
api.interceptors.request.use(config => {
  //获取请求协议+路径
  const requestUrl = `${config.method} ${config.url}`;
  //查找取消请求标识中是否存在相同的requestUrl
  const cancelIndex = cancelToken.findIndex(item => {
    return item.url === requestUrl;
  });

  //存在就说明重复了
  if (cancelIndex > -1) {
    //取消
    cancelToken[cancelIndex].cancel();
    //删除该标识
    cancelToken.splice(cancelIndex, 1);
  };
  
  //添加响应拦截中成功后删除该标识的判断条件
  config.requestUrl = requestUrl;
  
  //传入本地取消请求
  cancelToken.push({
    url: requestUrl,
    cancel: source.cancel,
  });

  return config;
}, error => Promise.reject(error));



//axios响应拦截器
api.interceptors.response.use(response => {
  //响应拦截器删除取消标识中已完成的请求
  const cancelIndex = cancelToken.findIndex(item => {
    return item.url === response.config.requestUrl
  });

  if (cancelIndex > -1) {
    cancelToken.splice(cancelIndex, 1);
  };

  //其他自定义的操作
  ....
}, error => Promise.reject(error));

不管成功还是失败,只要这个请求响应了,我们就可以删除它的记录。

axios的响应拦截器中,如果返回的状态码不在2xx范围内,它会走error方法,所以,我们还需要考虑到在error中删除记录。

既然两边都要,我们可以把方法提取出来。

//响应拦截器删除取消标识中已完成的请求
const responseDeleteCancelToken = config => {
  const cancelIndex = cancelToken.findIndex(item => {
    return item.url === config.requestUrl
  });

  if (cancelIndex > -1) {
    cancelToken.splice(cancelIndex, 1);
  };
};

//axios响应拦截器
api.interceptors.response.use(response => {
  //响应拦截器删除取消标识中已完成的请求
  responseDeleteCancelToken(response.config)

  //其他自定义的操作
  ....
}, error => {
  //响应拦截器删除取消标识中已完成的请求
  const response = error.response;
  // 请求已发出,但服务器响应的状态码不在 2xx 范围内
  if (response) {
    //删除取消标识
    responseDeleteCancelToken(response.config);
  }
  
  return Promise.reject(error);
});

这样,我们在两种状态中都对应做了处理。

补充一个小功能:

当我们cancel取消axios请求时,其实会出发响应拦截器的error方法,但是取消请求我们并不需要处罚catch方法的回调。

为了判断是否为取消的请求,axios提供了一个方法的,通过axios来判断error对象

axios.isCancel(error);  //true or false

但是我们在vue项目里面,一般都是模块化axios,不可能每个需要请求api的vue文件里面都import引入axios来判断,所以,我们在响应拦截器里面对error添加一个属性用于判断是否为取消的请求错误对象。

//响应拦截器删除取消标识中已完成的请求
const responseDeleteCancelToken = config => {
  const cancelIndex = cancelToken.findIndex(item => {
    return item.url === config.requestUrl
  });

  if (cancelIndex > -1) {
    cancelToken.splice(cancelIndex, 1);
  };
};

//axios响应拦截器
api.interceptors.response.use(response => {
  //响应拦截器删除取消标识中已完成的请求
  responseDeleteCancelToken(response.config)

  //其他自定义的操作
  ....
}, error => {
  //响应拦截器删除取消标识中已完成的请求
  const response = error.response;
  // 请求已发出,但服务器响应的状态码不在 2xx 范围内
  if (response) {
    //删除取消标识
    responseDeleteCancelToken(response.config);
  }

  //取消的请求给错误对象添加一个标识
  if (axios.isCancel(error)) {
    error.selfCancel = true;
  }
  
  return Promise.reject(error);
});

这样,我们在api请求的catch方法中,只要判断error.selfCancel属性就能知道是不是取消请求触发的,是的话就跳过。

vue路由切换取消上一个路由请求

我们目前axios内容如下:

import axios from "axios";

//创建自定义axios示例
let api = axios.create({});

//取消请求的数组
let cancelToken = [];

//请求前拦截
api.interceptors.request.use(config => {
  //获取请求协议+路径
  const requestUrl = `${config.method} ${config.url}`;
  //查找取消请求标识中是否存在相同的requestUrl
  const cancelIndex = cancelToken.findIndex(item => {
    return item.url === requestUrl;
  });

  //存在就说明重复了
  if (cancelIndex > -1) {
    //取消
    cancelToken[cancelIndex].cancel();
    //删除该标识
    cancelToken.splice(cancelIndex, 1);
  };
  
  //添加响应拦截中成功后删除该标识的判断条件
  config.requestUrl = requestUrl;
  
  //传入本地取消请求
  cancelToken.push({
    url: requestUrl,
    cancel: source.cancel,
  });

  return config;
}, error => Promise.reject(error));



//响应拦截器删除取消标识中已完成的请求
const responseDeleteCancelToken = config => {
  const cancelIndex = cancelToken.findIndex(item => {
    return item.url === config.requestUrl
  });

  if (cancelIndex > -1) {
    cancelToken.splice(cancelIndex, 1);
  };
};

//axios响应拦截器
api.interceptors.response.use(response => {
  //响应拦截器删除取消标识中已完成的请求
  responseDeleteCancelToken(response.config)

  //其他自定义的操作
  ....
}, error => {
  //响应拦截器删除取消标识中已完成的请求
  const response = error.response;
  // 请求已发出,但服务器响应的状态码不在 2xx 范围内
  if (response) {
    //删除取消标识
    responseDeleteCancelToken(response.config);
  }

  //取消的请求给错误对象添加一个标识
  if (axios.isCancel(error)) {
    error.selfCancel = true;
  }
  
  return Promise.reject(error);
});

export { api, cancelToken };

我们讲cancelToken 导出了。

在vue-router路由守卫中引入并使用:

import { cancelToken } from "@/utils/request";

//路由进入前
router.beforeEach((to, from, next) => {
    //取消上个路由的请求
    cancelToken.forEach(item => {
      item.cancel();
    });

    next();
  });

目前我们是取消了所有在cancelToken 中的请求。

现在我们要做的是,如何不取消全局性的请求?

如何不取消全局性的请求?

最根本的办法就是加入判断条件, 我们可以在push到cancelToken数组的对象中,添加一个属性,表示这个请求是全局的请求。

//记录本次请求
  cancelToken.push({
    url: requestUrl,
    cancel: source.cancel,
    routeChangeCancel: true
  });

默认情况我们可以将routeChangeCancel设为true,表示这个请求可以在路由切换时取消。

那么我们怎么配置这个属性呢?

可以通过axios的defaults默认配置添加一个routeChangeCancel

axios.create({//我就是defaults});

眼熟吧!

当然这个对象还能是这样传入

import axios from "axios";

//创建自定义axios示例
let api = axios.create({});

api.get("xxxx",{//我就是defaults});
import axios from "axios";

//创建自定义axios示例
let api = axios.create({});
api.defaults.routeChangeCancel = true;

很多种方式可以设置。

获取这个值得时候可以在拦截器中的config获取。

config.routeChangeCancel

于是乎改造:

import axios from "axios";

//创建自定义axios示例
let api = axios.create({
  routeChangeCancel: true, //默认所有请求都可以在路由页切换时取消
});

//取消请求的数组
let cancelToken = [];

//请求前拦截
api.interceptors.request.use(config => {
  //获取请求协议+路径
  const requestUrl = `${config.method} ${config.url}`;
  //查找取消请求标识中是否存在相同的requestUrl
  const cancelIndex = cancelToken.findIndex(item => {
    return item.url === requestUrl;
  });

  //存在就说明重复了
  if (cancelIndex > -1) {
    //取消
    cancelToken[cancelIndex].cancel();
    //删除该标识
    cancelToken.splice(cancelIndex, 1);
  };
  
  //添加响应拦截中成功后删除该标识的判断条件
  config.requestUrl = requestUrl;
  
  //传入本地取消请求
  cancelToken.push({
    url: requestUrl,
    cancel: source.cancel,
    routeChangeCancel: config.routeChangeCancel
  });

  return config;
}, error => Promise.reject(error));



//响应拦截器删除取消标识中已完成的请求
const responseDeleteCancelToken = config => {
  const cancelIndex = cancelToken.findIndex(item => {
    return item.url === config.requestUrl
  });

  if (cancelIndex > -1) {
    cancelToken.splice(cancelIndex, 1);
  };
};

//axios响应拦截器
api.interceptors.response.use(response => {
  //响应拦截器删除取消标识中已完成的请求
  responseDeleteCancelToken(response.config)

  //其他自定义的操作
  ....
}, error => {
  //响应拦截器删除取消标识中已完成的请求
  const response = error.response;
  // 请求已发出,但服务器响应的状态码不在 2xx 范围内
  if (response) {
    //删除取消标识
    responseDeleteCancelToken(response.config);
  }

  //取消的请求给错误对象添加一个标识
  if (axios.isCancel(error)) {
    error.selfCancel = true;
  }
  
  return Promise.reject(error);
});

export { api, cancelToken };

设置好标识后,我们在路由守卫里加上判断:

import { cancelToken } from "@/utils/request";

//路由进入前
router.beforeEach((to, from, next) => {
    //取消上个路由的请求
    cancelToken.forEach(item => {
      item.routeChangeCancel && item.cancel();
    });

    next();
  });

于是乎,一切都完美了。

目前来说这种方式满足了很多情况下的取消请求,感谢那篇文章的作者,让我对请求的取消方式,有了新的理解。

补充冷知识

你以为你取消了请求,后端就接不到了?

nonono,大错特错

虽然axios的取消请求确实用了xhr的abort()取消,但是这个取消是看时机的,如果请求已经发出了才取消,那么后端还是可以接收到你的请求,如果在发出前就取消了,那么后端就收不到了。

那么后端一般是怎么处理这种的呢?

如果检测到你的请求被取消了,那么后端会取消发送。

没错,是取消发送,不是停止查询,所以实际上我们取消请求后,后端接受到了,还是会触发它的代码运行,只不过在返回结果的时候会判断,如果连接断了,就不在发送结果了。


以上就是关于vue中axios取消请的知识。

分类: vue 项目实战 标签: vueaxios取消请求

评论

暂无评论数据

暂无评论数据

目录