vue切换路由时,取消所有axios请求

需求场景,由于某些请求很耗时,比如大文件上传、下载,当用户在当前页面发起请求后,又不想等待了,想去其他页面看看。如果不取消此类请求,就会在下一个页面莫名其妙地弹出成功或失败结果,也会影响页面响应。

其实,要不要取消前一个页面的请求是根据实际情况确定的,如果希望在浏览其他页面时文件继续上传,就不能取消。现在,我们只讨论下需要取消上个页面请求的代码实现:

一、看下axios官网的介绍:

可以使用 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();

可以看出,取消请求需要两个步骤,一是请求配置设置cancelToken,二是取消时调用cancel方法。

官网提供的两种方式,第一种通过CancelToken类的source方法,返回具体的cancelToken和对应的cancel方法;第二种通过CancelToken的构造函数new CancelToken(executor),返回具体的cancelToken,同时在executor回调中获得cancel方法。

二、查看axios源码:

对应第一种方式:可以看出source方法内部还是调用了CancelToken的构造函数,最后将token和对应的cancel封装成一个对象返回。

/**
 * Returns an object that contains a new `CancelToken` and a function that, when called,
 * cancels the `CancelToken`.
 */
CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  return {
    token: token,
    cancel: cancel
  };
};

对应第二种方式:可以看出 构造函数会将cancel方法当做参数传递给executor的回调函数,所以我们才可以在回调中获得cancel方法。

/**
 * A `CancelToken` is an object that can be used to request cancellation of an operation.
 *
 * @class
 * @param {Function} executor The executor function.
 */
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);
  });
}

 三、实现我们的代码

① 在请求拦截器处,设置cancelToken,并且将cancel方法绑定在Vue原型的$httpRequestList 数组上,在这里我还用MAX_CANCEL_TOKEN_NUM 对保存的cancel方法数量做了限制。

const CANCEL_TOKEN = axios.CancelToken;
const MAX_CANCEL_TOKEN_NUM = 2000;
Vue.prototype.$httpRequestList = [];

axios.interceptors.request.use(
  config => {
    config.CancelToken = new CANCEL_TOKEN(c => {
      if (Vue.prototype.$httpRequestList.length == MAX_CANCEL_TOKEN_NUM) {
        Vue.prototype.$httpRequestList.pop();
      }
      Vue.prototype.$httpRequestList.unshift(c);
    })
    return config;
  },
  err => {
    return Promise.reject(err);
  });

② 路由切换时,遍历 Vue.prototype.$httpRequestList 执行每一个cancel方法。 是不是放在afterEach方法中有待斟酌。。。

/* 全局后置钩子 */
router.afterEach(function (to, from) {
  Vue.prototype.$httpRequestList.forEach(cancel => {
    cancel()
  });
});

参考:http://www.axios-js.com/zh-cn/docs/#取消

          https://github.com/axios/axios/blob/master/lib/cancel/CancelToken.js

 

posted @ 2021-03-16 10:57  yuanxv  阅读(1245)  评论(0编辑  收藏  举报