axios源码分析--取消请求

1,898 阅读2分钟

使用方法

方式一

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 {
    // handle error
  } 
});

source.cancel('Operation canceled by the user.');

方式二

const CancelToken = axios.CancelToken;
let cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    cancel = c;
  })
});

cancel();

源码分析

axios/lib/axios.js中定义了跟取消相关的方法

var axios = createInstance(defaults);
...
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
...

我先看CancelToken,进入到axios/lib/cancel/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(cancel方法);
  executor(function cancel(message) {
    if (token.reason) {
      // Cancellation has already been requested
      return;
    }
    //token.reason是Cancel的实例
    token.reason = new Cancel(message);
    resolvePromise(token.reason);//改变promise的状态
  });
}

CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  return {
    token: token,  //CancelToken的实例
    cancel: cancel //function cancel(message) {...}
  };
};

CancelToken.prototype.throwIfRequested = function throwIfRequested() {
  if (this.reason) {
    throw this.reason;
  }
};

CancelToken构造函数,定义了一个promise,状态一直是padding,将决定状态的resolve方法传递cancel方法内部,cancel方法作为参数传到外部executor方法中。

CancelToken.source实际上new 一个CancelToken的实例,上述的cancel方法传给cancel变量

进入到axios/lib/cancel/Cancel.js文件

function Cancel(message) {
  this.message = message;
}

Cancel.prototype.toString = function toString() {
  return 'Cancel' + (this.message ? ': ' + this.message : '');
};

Cancel.prototype.__CANCEL__ = true;

Cancel构造函数,有属性message,Cancel.prototype有属性__CANCEL__

进入到axios/lib/cancel/isCancel.js文件

function isCancel(value) {
  return !!(value && value.__CANCEL__);
};

思考

如何一步步实现取消的?

function throwIfCancellationRequested(config) {
  //判断是否配置有cancelToken,
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }
}

function dispatchRequest(config) {
  throwIfCancellationRequested(config);//请求前
  ...
  return adapter(config).then(function onAdapterResolution(response) {
    throwIfCancellationRequested(config);//请求后,resolve
    ...
  }, function onAdapterRejection(reason) {
    if (!isCancel(reason)) { //请求后,reject
      throwIfCancellationRequested(config);
    ...  
 });
}
  

调用throwIfCancellationRequested方法,内部实现先判断是否配置有cancelToken,则调用CancelToken.prototype.throwIfRequested,如果调用cancel操作过了肯定reason存在,抛出throw reason,reason实际上是Cancel的实例

axios/lib/adapters/xhr.js

function xhrAdapter(config) {
    return new Promise(function dispatchXhrRequest(resolve, reject) {
        ...
        if (config.cancelToken) {
        //请求中,监听cancelToken中promise状态改变
          config.cancelToken.promise.then(function onCanceled(cancel) {
            if (!request) {
              return;
            }
        
            request.abort();
            reject(cancel);
            request = null;
          });
        }
        ...
    }
}

总结: CancelToken.source().token实际可用理解为new 一个promise,状态一直是 padding。把可用改变状态方法传到cancel方法中。外部调用cancel实现取消请求。

cancel操作,其实是执行resolvePromise,让cancelToken中promise状态变为已完成。实际上在发送请求前、请求中,请求后resolve、请求后reject四部分,都会去判断是否有cancel操作。

  • 发送请求前,如果有cancel操作,说明reason存在,则throw reason, reason对象(实际是Cancel的实例)
  • 请求中,如果有cancel操作,说明已经建立xhr,则要abort这个请求,所以要再xhr中有config.cancelToken.promise.then等待promise状态改变
  • 请求后,如果有cancel操作,与发送请求前处理方式一样