【axios三部曲】二、核心源码解析

目录结构

image.png

我们主要看lib里面的源码:

  • adapter 适配器,用于处理浏览器或者node环境的适配
  • cancel 取消请求
  • core 核心源码
  • helpers 一些工具帮助函数
  • axios.js 入口文件
  • defaults.js 默认配置文件
  • utils.js 工具函数

axios.js入口文件

我们从入口文件开始

'use strict';
// axios 入口文件
var utils = require('./utils');	// 工具函数
var bind = require('./helpers/bind'); // 绑定函数
var Axios = require('./core/Axios');	// 核心Axios
var mergeConfig = require('./core/mergeConfig');	// 合并对象函数
var defaults = require('./defaults');	// 默认配置

/**
 * 用于创建一个Axios的实例对象,Axios实际上是一个构造函数
 * 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的实例对象,但是我们需要的并不是它,我们要的是它原型上的request方法
  	// 这里把这个context当作上下文过渡
  	// 如果不清楚Axios具体是什么,可以先看文章后面的解析
    var context = new Axios(defaultConfig);
  	
  	// 重点:绑定Axios原型上的request方法,并指定this指向
  	// axios(),实际上是一个函数,就是Axios.prototype.request
    var instance = bind(Axios.prototype.request, context);
  
  	// axios光是一个函数还不够,还需要这样使用:axios.get(url[,config]);
  	// 于是我们就在这个函数身上添加Axios原型上有的prototype方法进来
  	// get,delete,head,options,post,put,patch...
    utils.extend(instance, Axios.prototype, context);
  
  	// 还少了一些Axios原有的属性,也添加进来(defaults,interceptors)
    utils.extend(instance, context);
  
    return instance;
}

var axios = createInstance(defaults);

axios.Axios = Axios;

axios.create = function create(instanceConfig) {
    return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

// 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 = require('./helpers/spread');

module.exports = axios;

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

这里有一些工具函数,对于它的作用还不是很清楚,我们就来仔细看一下:

  • bind
  • utils.extend
  • ...

axios常用的工具函数

这些工具函数可以结合源码上下文来看,效果更好

bind(fn, thisArg)

// 绑定函数的this指向
function bind(fn, thisArg){
	return function wrap(){
    // 把多个参数传入数组
  	let args = new Array(arguments.length);
    for(let i=0;i<args.length;i++){
    	args[i] = arguments[i];
    }
    
    return fn.apply(thisArg, args);
  }
}

forEach(obj, fn)

// 遍历一个Array或者一个Object
function forEach(obj, fn){
  if(obj === null || typeof obj === 'undefined'){
  	return;
  }
  
  // 如果不是object,放到数组中
  if(typeof obj !== 'object'){
    obj = [obj]
  }
  
  if(Array.isArray(obj)){
    for(let i=0;i<obj.length;i++){
      // 回调函数,this指向为null
      fn.call(null, obj[i], i, obj);
    }
  }else{
    for(let key in obj){
    	// 这里需要判断是否是自己身上的属性,不然会循环到prototype上去
      if(Object.prototype.hasOwnProperty(obj, key)){
        fn.call(null, obj[key], key, obj);
      }
    }
  }
}

extend(obj1, obj2, thisArg)

// 把obj2的属性和方法扩展到obj1
function extend(obj1, obj2, thisArg){
  // 遍历obj2
  forEach(obj2, function(val, key){
    // 如果有thisArg参数,并且val值为函数
  	if(thisArg && typeof val === 'function'){
      // 需要绑定this指向
      obj1[key] = bind(thisArg, val);
    }else{
    	obj1[key] = val;
    }
  });
  
  return obj1;
}

merge(​)

// 合并对象函数,优先级从右往左,也就是后面的参数优先级越高
function merge(/* obj1, obj2, obj3, ... */){
	let result = {};
  function assignValue(val, key){
    // b->a
    // a对象的一个key对应的值是object,并且b对象的值也是一个对象
    // 递归调用
  	if(typeof result[key] === 'object' && typeof val === 'object'){
    	result[key] = merge(result[key], val);
    }else if(typeof val === 'object'){
    	result[key] = merge({}, val);
    }else{
    	result[key] = val;
    }
  }
  
  for(let i=0;i<arguments.length;i++){
  	forEach(arguments[i], assignValue);
  }
  
  return result;
}

Axios核心函数

core/Axios.js

'use strict';
//Axios 构造函数


var utils = require('./../utils');
var buildURL = require('../helpers/buildURL');
var InterceptorManager = require('./InterceptorManager'); // 拦截器
var dispatchRequest = require('./dispatchRequest');	// 请求派发
var mergeConfig = require('./mergeConfig');	// 合并config函数

/**
 * Create a new instance of Axios
 * @param {Object} instanceConfig The default config for the instance
 */
function Axios(instanceConfig) {
  	// 实例上的默认配置
    this.defaults = instanceConfig;
  	// 实例上的拦截器
    this.interceptors = {
        request: new InterceptorManager(),
        response: new InterceptorManager()
    };
}

/**
 * 核心方法request
 * 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
    /**
     * axios('http://www.baidu.com', {header:{}})
     */
  	// 对传入的参数做处理,支持axios(url,[,config]) 这中传参写法
    if (typeof config === 'string') {
        config = arguments[1] || {};
        config.url = arguments[0];
    } else {
        config = config || {};
    }
  	// 合并默认的参数和传入的参数
    config = mergeConfig(this.defaults, config);

    // Set config.method
    // 指定默认的请求方式get
    if (config.method) {
        config.method = config.method.toLowerCase();
    } else if (this.defaults.method) {
        config.method = this.defaults.method.toLowerCase();
    } else {
        config.method = 'get';
    }

    // Hook up interceptors middleware
    // 请求链,一个数组来维护发出真实请求前后要处理的事
    // 最终chain会长这样:
    // [请求拦截器1,请求拦截器2,..., dispatchRequest, undefined, 响应拦截器1,响应拦截器2...]
    var chain = [dispatchRequest, undefined];	// 派发请求,undefined占位用的
    var promise = Promise.resolve(config);//  promise 成功的Promise,带有config返回到下一个promise
  
    // 请求拦截器添加到数组的头,因为是请求拦截器嘛
    this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
        chain.unshift(interceptor.fulfilled, interceptor.rejected);
    });

    // 响应拦截器添加到数组的尾,最终返回的结果就是它,依然是promise
    this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
        chain.push(interceptor.fulfilled, interceptor.rejected);
    });

    // chain数组把要处理的事件方法排好了,接下来就是依次执行了
    while (chain.length) {
      	// 数组执行一个,就弹出数组
        promise = promise.then(chain.shift(), chain.shift());
    }

    return promise;
};

// 获取uri地址
Axios.prototype.getUri = function getUri(config) {
    config = mergeConfig(this.defaults, config);
    return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');
};

// 别名方法delete, get, head, options, 实际上也是调用request
// Provide aliases for supported request methods  axios.get  axios.post axios.put
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
        }));
    };
});

// 与上面方法不同的是,需要传data
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;

InterceptorManager 拦截器

我们来看看拦截器做了些什么
core/InterceptorManager.js

'use strict';
var utils = require('./../utils');

// 拦截器的构造函数
function InterceptorManager() {
  // 实例属性handlers数组
  this.handlers = [];
}

/**
 * 把一个新的拦截器添加到这个实例的handlers数组上
 * 参数(fulfilled, rejected)其实是一个成功promise函数和一个失败的promise函数
 * 返回一个id,用作移除
 * Add a new interceptor to the stack
 * @param {Function} fulfilled The function to handle `then` for a `Promise`
 * @param {Function} rejected The function to handle `reject` for a `Promise`
 *
 * @return {Number} An ID used to remove interceptor later
 */
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

/**
 * 通过id移除拦截器
 * Remove an interceptor from the stack
 * @param {Number} id The ID that was returned by `use`
 */
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};

/**
 * 迭代所有注册的拦截器
 * Iterate over all the registered interceptors
 * This method is particularly useful for skipping over any
 * interceptors that may have become `null` calling `eject`.
 *
 * @param {Function} fn The function to call for each interceptor
 */
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};

module.exports = InterceptorManager;

dispatchRequest 请求派发

到目前为止,我们并没有真实的发送一个请求,dispatchRequest也并不发送请求,它会去调用适配器(adapter)来做这件事(发送请求)
core/dispatchRequest.js

'use strict';
//发送请求的函数文件

var utils = require('./../utils');
var transformData = require('./transformData'); // 转换数据
var isCancel = require('../cancel/isCancel'); // 取消函数
var defaults = require('../defaults');

/**
 * 如果config配置里面有cancelToken配置,就抛一个异常
 * Throws a `Cancel` if cancellation has been requested.
 */
function throwIfCancellationRequested(config) {
    if (config.cancelToken) {
        config.cancelToken.throwIfRequested();
    }
}

/**
 * 派发一个请求 使用config里面的适配器
 * Dispatch a request to the server using the configured adapter.
 * @param {object} config The config that is to be used for the request
 * @returns {Promise} The Promise to be fulfilled
 */
module.exports = function dispatchRequest(config) {
    // 如果配置了取消请求
    throwIfCancellationRequested(config);

    // Ensure headers exist
    // 确保headers头存在
    config.headers = config.headers || {};

    // 转换请求data数据
    config.data = transformData(
        config.data,
        config.headers,
        config.transformRequest
    );

    // 扁平化,合并头
    config.headers = utils.merge(
        config.headers.common || {},
        config.headers[config.method] || {},
        config.headers
    );

    // 移除头里面的这些方法,
    // 既然要开始一个请求了,请求的配置config还是要做一些清理工作
    utils.forEach(
        ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
        function cleanHeaderConfig(method) {
            delete config.headers[method];
        }
    );

    // 适配器,如果config里面设置了优先使用,否则用默认的
    var adapter = config.adapter || defaults.adapter;

    // 适配器返回一个promise,这是一个真正的请求
    return adapter(config).then(function onAdapterResolution(response) {
        throwIfCancellationRequested(config);

        // Transform response data
        response.data = transformData(
            response.data,
            response.headers,
            config.transformResponse
        );
        return response;
    }, function onAdapterRejection(reason) {
        if (!isCancel(reason)) {
            throwIfCancellationRequested(config);

            // Transform response data
            if (reason && reason.response) {
                reason.response.data = transformData(
                    reason.response.data,
                    reason.response.headers,
                    config.transformResponse
                );
            }
        }
        return Promise.reject(reason);
    });
};

mergeConfig合并对象

core/mergeConfig.js

'use strict';
var utils = require('../utils');

/**
 * 合并两个对象,返回一个新对象
 * Config-specific merge-function which creates a new config-object
 * by merging two configuration objects together.
 * @param {Object} config1
 * @param {Object} config2
 * @returns {Object} New object resulting from merging config2 to config1
 */
module.exports = function mergeConfig(config1, config2) {
  // eslint-disable-next-line no-param-reassign
  config2 = config2 || {};
  var config = {};

  // 哪些值来自config2上的key
  var valueFromConfig2Keys = ['url', 'method', 'params', 'data'];
  // 需要深拷贝的值
  var mergeDeepPropertiesKeys = ['headers', 'auth', 'proxy'];
  // config2的默认key
  var defaultToConfig2Keys = [
    'baseURL', 'url', 'transformRequest', 'transformResponse', 'paramsSerializer',
    'timeout', 'withCredentials', 'adapter', 'responseType', 'xsrfCookieName',
    'xsrfHeaderName', 'onUploadProgress', 'onDownloadProgress',
    'maxContentLength', 'validateStatus', 'maxRedirects', 'httpAgent',
    'httpsAgent', 'cancelToken', 'socketPath'
  ];

  // 这些值来自config2上
  utils.forEach(valueFromConfig2Keys, function valueFromConfig2(prop) {
    if (typeof config2[prop] !== 'undefined') {
      config[prop] = config2[prop];
    }
  });

  // 优先使用config2上的,如果值是对象就深拷贝
  utils.forEach(mergeDeepPropertiesKeys, function mergeDeepProperties(prop) {
    if (utils.isObject(config2[prop])) {
      config[prop] = utils.deepMerge(config1[prop], config2[prop]);
    } else if (typeof config2[prop] !== 'undefined') {
      config[prop] = config2[prop];
    } else if (utils.isObject(config1[prop])) {
      config[prop] = utils.deepMerge(config1[prop]);
    } else if (typeof config1[prop] !== 'undefined') {
      config[prop] = config1[prop];
    }
  });

  // 设置默认值,优先使用config2上的
  utils.forEach(defaultToConfig2Keys, function defaultToConfig2(prop) {
    if (typeof config2[prop] !== 'undefined') {
      config[prop] = config2[prop];
    } else if (typeof config1[prop] !== 'undefined') {
      config[prop] = config1[prop];
    }
  });

  // 把所有的key合并到一个数组
  var axiosKeys = valueFromConfig2Keys
    .concat(mergeDeepPropertiesKeys)
    .concat(defaultToConfig2Keys);

  // 如果有axiosKeys以外的其他key,相当于用户自定义头
  // 这里用了set,数组去重
  var otherKeys = Array.from(
    new Set(Object.keys(config1).concat(Object.keys(config2)))
  ).filter((key) => {
    return axiosKeys.indexOf(key) === -1;
  });

  // 遍历其他keys,config2的优先级依然高于config1
  utils.forEach(otherKeys, function otherKeysDefaultToConfig2(prop) {
    if (typeof config2[prop] !== 'undefined') {
      config[prop] = config2[prop];
    } else if (typeof config1[prop] !== 'undefined') {
      config[prop] = config1[prop];
    }
  });

  // 返回一个合并的新的对象
  return config;
};

adapter适配器

这个适配器的作用是:判断是浏览器还是Node环境,
如果是浏览器环境,使用:XMLHttpRequest
如果是Node环境,使用:http,https
具体资料可以参考:XMLHttpRequesthttp超文本传输协议

axios执行的流程

1.png

posted @ 2022-02-26 16:36  Tonysoul  阅读(70)  评论(0编辑  收藏  举报