接口分析

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,找到源码
    1629275628(1).png
    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.js
    var 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
    1629273989(1).png

    function throwIfCancellationRequested(config) {
      if (config.cancelToken) {
     config.cancelToken.throwIfRequested();
      }
    }
  3. 追查throwIfCancellationRequested调用的地方,点击函数throwIfCancellationRequested,按F12,查看调用的地方
    1629274505.png
    1629274505(1).png
    1629274505(2).png
    也就是说,在初始化的时候、请求成功的时候以及请求失败的时候会调用,判断是否已经发起请求,如果已发起,则抛出取消原因。
    解决了用户调用cancel的时机,请求发起之前则拦截请求发起,请求发起之后,无论结果如何,都不会再进入处理逻辑。
  4. 向上追查,被promisify的xhr/http
    1629274912(1).png
    代码显示,在发起请求之前,判断是否传了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. 在设计一个子功能的时候,针对属性来扩展。

RockerLau
363 声望11 粉丝

Rocker Lau