py-tofee / Notes

2 stars 0 forks source link

Axios #17

Open py-tofee opened 3 years ago

py-tofee commented 3 years ago

axios的创建过程

'use strict';

var utils = require('./utils');
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var defaults = require('./defaults'); // 默认配置

/**
 * Create an instance of Axios
 *
 * @param {Object} defaultConfig The default config for the instance
 * @return {Axios} A new instance of Axios
 */
function createInstance(defaultConfig) {
  // 创建一个Axios的实例context
  var context = new Axios(defaultConfig); 
  // bind返回一个新函数,函数内的this指向context
  // instance就是request函数,可以用于发送请求
  var instance = bind(Axios.prototype.request, context); 

  // Copy axios.prototype to instance 将Axios原型上的方法和属性都拷贝到instance上
  utils.extend(instance, Axios.prototype, context);

  // Copy context to instance 将Axios实例context上的属性和方法都拷贝到instance上
  utils.extend(instance, context);

  return instance;
}

// Create the default instance to be exported
var axios = createInstance(defaults); // 使用默认配置创建的实例

// Expose Axios class to allow class inheritance
axios.Axios = Axios; // axios的Axios属性指向Axios

// Factory for creating new instances
// axios的工厂函数,用于创建自定义配置的axios
axios.create = function create(instanceConfig) {
  return createInstance(utils.merge(defaults, instanceConfig));
};
// ====================================
// axios.create([config])创建的新的axios没有下面的功能
// ====================================
// Expose Cancel & CancelToken
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');

// Expose all/spread 用于批量执行多个异步请求
axios.all = function all(promises) {
  return Promise.all(promises);
};
// axios.spread用来指定接收axios.all中每个请求返回的响应
axios.spread = require('./helpers/spread');

module.exports = axios;

// Allow use of default import syntax in TypeScript
module.exports.default = axios;

Axios

'use strict';

var defaults = require('./../defaults');
var utils = require('./../utils');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');

/**
 * Create a new instance of Axios
 *
 * @param {Object} instanceConfig The default config for the instance
 */
function Axios(instanceConfig) {
  this.defaults = instanceConfig; // 默认配置
  // InterceptorManager的实例上有一个handlers属性,默认是一个空数组,用于存放成功和失败的回调函数
  this.interceptors = { // 拦截器
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

/**
 * Dispatch a request
 *
 * @param {Object} config The config specific for this request (merged with this.defaults)
 */
Axios.prototype.request = function request(config) {
  /*eslint no-param-reassign:0*/
  // Allow for axios('example/url'[, config]) a la fetch API
  // 如果config是字符串,默认是get请求,第一个参数是URL,第二个参数是请求参数
  if (typeof config === 'string') {
    config = utils.merge({
      url: arguments[0]
    }, arguments[1]);
  }

  config = utils.merge(defaults, {method: 'get'}, this.defaults, config);
  config.method = config.method.toLowerCase(); // 转小写

  // Hook up interceptors middleware
  // chain : promise回调函数链
  /**
   * dispatchRequest分发请求,判断是用XHR(浏览器环境)还是HTTP(node环境)去发送请求;
   * undefined 用于占位,指请求失败的error回调函数
   * 为undefined表示promise状态为rejected时,在此不处理,透传到下一个promise.reject去处理
   * 通常是在响应拦截器中
   */
  var chain = [dispatchRequest, undefined];
  // config是一个常量,该promise变量调用then方法时,一定会调用成功的回调函数,即fulfilled
  var promise = Promise.resolve(config); 

  /**
   * this.interceptors.request.forEach:这个forEach是InterceptorManager原型上的方法,用于遍历请求拦截器中的handlers
   * 实际应用中使用时,axios.interceptors.request.use(fulfilled, rejected)
   * fulfilled:成功的回调
   * rejected:失败的回调
   * use是InterceptorManager原型上的方法,用于将{fulfilled, rejected}一组回调push到handlers中
   * 使用unshift方法,将请求拦截器中的handler一次放在chain的前面
   */
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });
   /**
    * 使用push方法,将请求拦截器中的handler一次放在chain的后面
    */
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });
  /**
   * 循环处理chain,chain = [请求拦截器2-成功, 请求拦截器2-失败, 请求拦截器1-成功, 请求拦截器1-失败, dispatchRequest, undefined, 响应拦截器1-成功,  响应拦截器1-失败, 响应拦截器2-成功, 响应拦截器2-失败]
   * 所以请求拦截器是先定义的后执行
   */
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

// Provide aliases for supported request methods
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

module.exports = Axios;

axios与Axios

axios严格来说不算是Axios的实例,axios是一个函数,且拥有Axios原型上的方法,和Axios实例上的属性;可以直接调用axios(config)发送请求,也可以axios.get(url, [,config]),axios.post(url, [,config])...发送请求(这是属于Axios原型上的方法,有被拷贝到axios上); Axios实例上的属性default和interceptors也被拷贝到axios中,所以可以通过axios.interceptors.response.use()添加拦截器

py-tofee commented 3 years ago

axios请求过程

调用axios()并不会立即发送请求,执行顺序说明:[请求拦截器2-成功, 请求拦截器2-失败, 请求拦截器1-成功, 请求拦截器1-失败, dispatchRequest, undefined, 响应拦截器1-成功, 响应拦截器1-失败, 响应拦截器2-成功, 响应拦截器2-失败],请求过程是由promise串联起来的,每一步都返回一个promise,请求拦截器之间传递的是config,发送请求之后,响应拦截器之间传递的是response;axios()即调用Axios.prototype.request()方法,最后返回一个promise,交由用户去处理返回的响应数据;

dispatchRequest

在dispatchRequest中,处理请求URL(整合baseURL和config.url),处理请求数据,合并请求头信息headers,调用合适的适配器adapter config.adapter || defaults.adapter,发送请求,并处理响应数据,最后返回一个promise

py-tofee commented 3 years ago

axios取消请求

let cancel;
// 请求之前,判断cancel是否为null
if (cancel && typeof cancel === 'function') {
  cancel('取消请求')
}
axios({
  url: '',
  cancelToken: new axios.CancelToken(function executor(c) {
    cancel = c; // 保存取消函数到cancel变量中,用于之后可能要取消当前请求
  })
}).then(
  // 请求响应后,将cancel置空
  response => {
    cancel = null
  },
  error => {
    cancel = null
  }
)

原理: axios.CancelToken = require('./cancel/CancelToken'); CancelToken函数,接收一个执行函数作为参数,执行new axios.CancelToken(executor),返回一个实例cancelToken,这个实例上有一个promise属性,将这个promise的resolve暴露给resolvePromise;调用传进来的执行函数executor(function cancel() {}),将取消请求的cancel函数暴露出去,由用户保存,例如上例中的cancel变量,当用户调用cancel()时,会执行resolvePromise; CancelToken函数:

function CancelToken(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  }

  var resolvePromise;
  // 添加实例属性promise
  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); // 调用resolvePromise,将会执行cancelToken实例上的`promise.then(fulfiled)`的成功回调函数
  });
}

原理: 使用axios(config)发送请求时,config.cancelToken被设置为 实例cancelToken,所以在适配器adapter处理请求时,判断config.cancelToken存在,执行实例cancelToken中的promise,当resolvePromise被执行时,该promise的成功回调函数会被执行,在成功回调函数中,调用request.abort();实现取消请求,取消后的请求,因为没有到达服务器,所以不会被响应拦截器拦截,但是可以使用全局的unhandledrejection事件监听到,或者用户自己的失败回调捕获到 在adapter,http和xhr中

if (config.cancelToken) {
      // Handle cancellation
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return;
        }

        request.abort();
        reject(cancel);
        // Clean up request
        request = null;
      });
    }