本文介绍使用cancelToken取消axios的请求

  1. 使用方法
import Vue from 'vue';
import Axios from 'axios';
import { Promise } from 'es6-promise';
import user from '../store/modules/user'
import server from '../config/hostConfig'

Axios.defaults.timeout = 45000; // 45s
// Axios.defaults.baseURL = server.target;
var CancelToken = Axios.CancelToken;
//配置取消数组
const urls = ['/api/test']
let pending = []
let removePending = (ever) => {
  for(let p in pending){
      if(pending[p].u === ever.url + '&' + ever.method) { //当当前请求在数组中存在时执行函数体
          pending[p].f(); //执行取消操作
          pending.splice(p, 1); //把这条记录从数组中移除
      }
  }
}
Axios.interceptors.request.use(function(config) {
  // Do something before request is sent
  //change method for get
  if (config['MSG']) {
    Vue.prototype.$showLoading(config['MSG']);
  } else {
    if (!config['NoLoading']) {
      // Vue.prototype.$showLoading();
    }
  }
  removePending(config); //在一个ajax发送前执行一下取消操作
  if(urls.includes(config.url)){
    config.cancelToken  = new CancelToken((c) =>{ // executor 函数接收一个 cancel 函数作为参数
      // 这里的ajax标识我是用请求地址&请求方式拼接的字符串,当然你可以选择其他的一些方式
           pending.push({ u: config.url + '&' + config.method, f: c });  
      })
  }

  if (user.state.token) { //用户登录时每次请求将token放入请求头中sso_token
    config.headers["sso_token"] = user.state.token;
    //config.headers["DZSWJ_TGC"] = user.state.token;
    if(config.url.indexOf('?') !== -1){
      config.url = config.url + '&sso_token='+user.state.token
    }else{
      config.url = config.url + '?sso_token='+user.state.token
    }
  }

 if (user.state.loginToken) { //用户登录时每次请求将token放入请求头中
    config.headers["sso_token"] = user.state.loginToken;
    if(config.url.indexOf('?') !== -1){
      config.url = config.url + '&sso_token='+user.state.token
    }else{
      config.url = config.url + '?sso_token='+user.state.token
    }
  }

  if (config['Content-Type'] === 'application/x-www-form-urlencoded;') { //默认发application/json请求,如果application/x-www-form-urlencoded;需要使用transformRequest对参数进行处理
    /*config['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';*/
    config.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
    config['transformRequest'] = function(obj) {
      var str = [];
      for (var p in obj)
        str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
      return str.join("&")
    };
  }
  //config.header['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';

  return config;
}, function(error) {
  // Do something with request error

  Vue.$vux.loading.hide()
  return Promise.reject(error);
});

Axios.interceptors.response.use(
  response => {
    removePending(response.config)
    if (response.config['Content-Type'] != 'multipart/form-data;') { //图片上传不关闭遮罩,由业务处理
      Vue.$vux.loading.hide();
    }
    if(response.data == '会话结束,请重新登录!'){
      return {message:'会话结束,请重新登录!'}
    }
    return response.data;
  },
  error => {
    Vue.$vux.loading.hide();
    if (Axios.isCancel(error)) {
      console.log(error.message);
      return 
    }
    if (error.response) {
      switch (error.response.status) {
        case 404:
          Vue.prototype.$alert("404未找到请求的资源");
          break;
        default:
          Vue.prototype.$alert('网络错误');
      }
    } else if (error instanceof Error) {
      console.error(error.message);
    } else {
      Vue.prototype.$alert(error.returnMsg);
    }

    return Promise.reject(error.response.data);
  });

export default Vue.prototype.$http = Axios;

如上代码所示。在请求拦截器中配置cancelToken。通过new CancelToken把取消函数放到pending数组中。
取消请求函数如下,当url与方法相同时会执行取消操作。达到取消重复请求的目的,效果如下图
在这里插入图片描述
我们配置了test请求的重复处理。那么连续发送test请求的时候就会取消上一次还没响应过来的请求。test2请求不处理,那么就不会取消test2请求。

let removePending = (ever) => {
  for(let p in pending){
      if(pending[p].u === ever.url + '&' + ever.method) { //当当前请求在数组中存在时执行函数体
          pending[p].f(); //执行取消操作
          pending.splice(p, 1); //把这条记录从数组中移除
      }
  }
}

响应成功之后需要移除取消请求函数。在响应拦截器中判断异常是否为取消请求,是的话取消之前异常处理流程

error => {
    Vue.$vux.loading.hide();
    if (Axios.isCancel(error)) { //判断异常是否为取消请求导致
      console.log(error.message);
      return 
    }
    if (error.response) {
      switch (error.response.status) {
        case 404:
          Vue.prototype.$alert("404未找到请求的资源");
          break;
        default:
          Vue.prototype.$alert('网络错误');
      }
    } else if (error instanceof Error) {
      console.error(error.message);
    } else {
      Vue.prototype.$alert(error.returnMsg);
    }

    return Promise.reject(error.response.data);
  }
  1. 原理简析
    为何配置了cancelToken能达到取消请求的目的呢?
    原因在axios源码的xhr.js发送请求部分有如下判断:
    if (config.cancelToken) {
      // Handle cancellation
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return;
        }

        request.abort();
        reject(cancel);
        // Clean up request
        request = null;
      });
    }

可以看到这里配置了cancelToken会进行一个判断。当promise状态变为resolve的时候会调用abort方法去取消请求。
那么如何让这个promise的状态变为resolve呢?见CancelToken.js下的如下代码:

function CancelToken(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  }

  var resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });

  var token = this;
  executor(function cancel(message) {
    if (token.reason) {
      // Cancellation has already been requested
      return;
    }

    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

当我们new CancelToken的时候会把executor的回调函数cancel传出,上文我们用pending数组保存了这个cancel函数。cancel函数可以调用到resolvePromise改变promise的状态为resolve。然后便会进入到上文中的then回调函数中进行请求取消。

Logo

前往低代码交流专区

更多推荐