接口分析

https://github.com/axios/axio...

应用形式一const CancelToken = axios.CancelToken;const source = CancelToken.source();axios.post('/user/12345', {  name: 'new name'}, {  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();

就是增加cancelToken参数,值是能够一个对象,有能够勾销的操作。

源码剖析

  1. 参考应用案例,定位axios.CancelToken,找到源码

    https://github.com/axios/axio...

    isCancel.js 判断是否cancel对象module.exports = function isCancel(value) {  return !!(value && value.__CANCEL__);};
    Cancel.js 结构cancel对象,用于标识cancel的message信息function Cancel(message) {  this.message = message;}Cancel.prototype.toString = function toString() {  return 'Cancel' + (this.message ? ': ' + this.message : '');};Cancel.prototype.__CANCEL__ = true;module.exports = Cancel;
    CancelToken.jsvar Cancel = require('./Cancel');function CancelToken(executor) {  // 执行器必须是函数  if (typeof executor !== 'function') { throw new TypeError('executor must be a function.');  }  var resolvePromise;  // 创立一个promise,并记录resolve办法,以便在执行器中应用  this.promise = new Promise(function promiseExecutor(resolve) { resolvePromise = resolve;  });  var token = this;  // cancel的外围办法:用reason来记录勾销起因,并用来判断是否曾经勾销过。并resolve掉下面的promise  executor(function cancel(message) { if (token.reason) {   // Cancellation has already been requested   return; } token.reason = new Cancel(message); resolvePromise(token.reason);  });}// 判断是否曾经勾销,如果勾销了,则抛出勾销起因CancelToken.prototype.throwIfRequested = function throwIfRequested() {  if (this.reason) { throw this.reason;   }};// 静态方法,返回一个实例// 应用形式二的实质就是以下的实现CancelToken.source = function source() {  var cancel;  var token = new CancelToken(function executor(c) { cancel = c;  });  return { token: token, cancel: cancel  };};module.exports = CancelToken;
  2. 搜寻代码cancelToken,定位ib/core/dispatchRequest.js

    function throwIfCancellationRequested(config) {  if (config.cancelToken) { config.cancelToken.throwIfRequested();  }}
  3. 追究throwIfCancellationRequested调用的中央,点击函数throwIfCancellationRequested,按F12,查看调用的中央



    也就是说,在初始化的时候、申请胜利的时候以及申请失败的时候会调用,判断是否曾经发动申请,如果已发动,则抛出勾销起因。
    解决了用户调用cancel的机会,申请发动之前则拦挡申请发动,申请发动之后,无论后果如何,都不会再进入解决逻辑。
  4. 向上追究,被promisify的xhr/http

    代码显示,在发动申请之前,判断是否传了cancelToken。如果传入了,则增加cancelToken的promise回调,把申请勾销掉。留神看看CancelToken.js,把cancel办法裸露进去了,用户调用cancel办法之后会把promise resolve掉。

从新梳理

  1. xhr/http,在发动申请之前,判断是否有传入cancelToken,并对promise传入回调,拦挡申请的发动。所以CancelToken.js须要有一个promise告知xhr/http用户是否曾经执行cancel了。
  2. dispatchRequest.js,在初始化、申请胜利、申请失败的时候,判断是否曾经被cancel了,如果是就抛出谬误。所以须要CancelToken.js的一个变量去判断是否曾经被cancel过,以及一个throwIfRequested办法去判断。
  3. 调用cancel的时刻,用户可能想做一些自定义的操作。所以执行器能够由用户本人传入,或者提供一个静态方法新建一个实例,且曾经写好了执行器了。

值得学习的中央

  1. promise咱们常常是依照规范的办法去调用的,在promise体内执行resolve,reject

    new Promsie((resolve, reject) => {  ...  if (...) {     resolve('xxx')   } else {     reject('yyy')   }})

    其实能够灵便点,用变量去保留resolve/reject,在里面执行,进步灵便度。

    let resolver;new Promsie((resolve, reject) => {  ...  resolver = resolve;})...resolver('xxx')...
  2. 函数的参数是函数,须要好好了解一下为什么能够在用户调用的时候,间接用t来示意CancelToken.js曾经写好的执行器

    function CancelToken(executor) {  ...  // executor函数调用  executor(function cancel(message) {     if (token.reason) {       return;     }     token.reason = new Cancel(message);     resolvePromise(token.reason);  });  ...}CancelToken.source = function source() {  var cancel;  // executor函数定义  var token = new CancelToken(function executor(c) { cancel = c;  });  return { token: token, cancel: cancel  };};

    简化一下

    var cancel;// 函数定义function executor(c) { cancel = c;}// 函数调用execotr(() => {console.log('abc'})

    也就是说,在定义函数的时候,把一个全局变量保留局部变量的信息,这样是为了援用用户传入的信息。
    因为存在变量援用,所以我认为executor也是闭包。

  3. 在设计一个子性能的时候,针对属性来扩大。