当前位置:
首页
文章
移动端
详情

axios之cancelToken原理及使用

在真实项目中,当路由已经跳转,而上一页的请求还在pending状态,如果数据量小还好,数据量大时,跳到新页面,旧的请求依旧没有停止,这将会十分损耗性能,这时我们应该先取消掉之前还没有获得相应的请求,再跳转页面。

在真实项目中,当路由已经跳转,而上一页的请求还在pending状态,如果数据量小还好,数据量大时,跳到新页面,旧的请求依旧没有停止,这将会十分损耗性能,这时我们应该先取消掉之前还没有获得相应的请求,再跳转页面。这里axios给我们提供了一个方法:

基本使用

我们先来看看基本用法:

    var CancelToken = axios.CancelToken;
    var source = CancelToken.source();
    axios.get('/user/12345', {//get请求在第二个参数
       cancelToken: source.token
    }).catch(function(thrown) {
    });
    axios.post('/user/12345', {//post请求在第三个参数
       name: 'new name'
    }, {
       cancelToken: source.token
    });
    source.cancel('不想请求了');  //主动取消

注意,get请求的时候,cancelToken是放在第二个参数里;post的时候,cancelToken是放在第三个参数里

切换页面取消

//切换页面取消请求
let source = axios.CancelToken.source();

axios.interceptors.request.use((request) => {
  request.cancelToken = source.token;
  return request;
});
router.then((lib) => {
  lib.default.beforeEach((to, from, next) => {
    source.cancel();
    source = axios.CancelToken.source();
    next();
  });
})

取消请求之后,会进入error,在里面处理一下如果是取消请求,就不报错,

    axios.interceptors.response.use(
      (response) => {
         return response;
    }, 
      (error) => {
        if (axios.isCancel(error)) {
     
            console.log( 'request cancel ', JSON.stringify(error))
            return new Promise(() =>{})
        }
        return Promise.reject(error)
    })

实现原理

promise只有三种状态,pending(等待),resolved(成功),rejected(失败)。

它的本质就是在解决如何取消一个已经执行的promise本身。

我们可以发现,它要先引用axios.CancelToken,然后调用source()方法,会产生一个token和cancel,它的内部到底如何实现,这样做的目的是什么?

源码分析

现在我们来看看cancelToken的源码:

function Cancel(message) {
  this.message = message; // 可以传递因何取消请求的message
}

function CancelToken(executor) {
  // 判断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;
  // 以上token现在有一个promise属性,是一个未成功的promise对象;
  executor(function cancel(message) {
    if (token.reason) {
      // Cancellation has already been requested
      return;
    }

    token.reason = new Cancel(message);
    resolvePromise(token.reason); //关键代码二: 把接收到的“取消请求信息”token.reason传递给下一个then中成功函数作为参数
  });
}

CancelToken.prototype.throwIfRequested = function throwIfRequested() {
  if (this.reason) {
    throw this.reason;
  }
};
// source 方法
CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c; // 关键代码三:实现“在他处取消请求”,与上面“终止一个promise”异曲同工。
  });
  return {
    token: token,
    cancel: cancel
  };
};

module.exports = CancelToken;

通过源码我们可以发现,CancelToken这个类初始化的时候需要传递一个方法executor,并且它的内部新建了一个promise,最关键的是,它把promise的resolve方法控制权放在了executor方法里面!这种操作代表什么意思?我们看一个小例子:

    let resolveHandle;
    new Promise((resolve)=>{
        resolveHandle=resolve;
    }).then((val)=>{
     console.log('resolve',val);
    });
    resolveHandle('ok');

上面的例子中,我们用resolveHandle获取了一个promise的resolve方法的控制权,这样,我们就可以在外部控制这个promise的成功了。要知道new Promise返回的对象是无法从外部决定它成功还是失败的。

现在来看看source这个方法,我们可以看到,它new了一个CancelToken对象,并传了一个方法executor;采用相同的手法,用cancel变量将executor方法的变量c的控制权拿出来了,那么这个变量c又代表啥呢?

变量c正是我们前面说到的在CancelToken初始化时,传入executor方法的,也即:

    function cancel(message) {
        if (token.reason) {
        // Cancellation has already been requested
        return;
     }
        token.reason = new Cancel(message);
     resolvePromise(token.reason);
    }

也就是说cancel代表的是上面的这个方法,有了这个方法,就可以在外部控制CancelToken内部的promise对象了。

在source方法中,除了cancel,还有一个token,这个token是CancelToken类的一个实例,可以访问到内部的promise。

因此CancelToken类如此封装的主要目的就是为了能够分离promise和resolve方法,让用户可以自己调用resolve方法。一旦resolve后,就会触发promise的then方法,现在看看内部promise后的then方法是什么:

   // 在发送请求之前,验证了cancelToken,看来此处就是用来取消请求的;
if(config.cancelToken){
    // 具体是如何取消的,是在这个判断内定义的;
    config.cancelToken.promise.then(function(cancel){
        request.abort();
        reject(cancel);
        request = null;
    })
}
// 发送请求
request.send(requestData);

上面是xhr.js的关于cancelToken部分相关代码,可以看到,当用户调用cancel后,就会立即执行abort方法取消请求,同时调用reject让外层的promise失败。

免责申明:本站发布的内容(图片、视频和文字)以转载和分享为主,文章观点不代表本站立场,如涉及侵权请联系站长邮箱:xbc-online@qq.com进行反馈,一经查实,将立刻删除涉嫌侵权内容。