axios源码的学习

119 阅读4分钟

axios源码学习

前言

本文是学习小白初学源码的自用笔记,参考自若川大佬的学习axios源码整体架构,打造属于自己的请求库

axios源码初始化

lib/axios.js主文件

axios.js主文件主要分为三部分组成:

第一部分:引入一些工具函数utils,Axios构造函数、默认配置项defaults等。

第二部分:生成实例对象axiosaxios.Axiosaxios.create等

第三部分:取消相关api实现,还有all,spread,导出等实现

第一部分

引入一些工具函数utils,Axios构造函数、默认配置项defaults

// 第一部分:
// lib/axios
// 严格模式
'use strict';
// 引入 utils 对象,有很多工具方法。
var utils = require('./utils');
// 引入 bind 方法
var bind = require('./helpers/bind');
// 核心构造函数 Axios
var Axios = require('./core/Axios');
// 合并配置方法
var mergeConfig = require('./core/mergeConfig');
// 引入默认配置
var defaults = require('./defaults');

第二部分

生成实例对象axiosaxios.Axiosaxios.create

/**
 * Create an instance of Axios
 *
 * @param {Object} defaultConfig The default config for the instance
 * @return {Axios} A new instance of Axios
 */
function createInstance(defaultConfig) {
  // new 一个 Axios 生成实例对象
  var context = new Axios(defaultConfig);
  // bind 返回一个新的 wrap 函数,
  // 也就是为什么调用 axios 是调用 Axios.prototype.request 函数的原因
  var instance = bind(Axios.prototype.request, context);
  // Copy axios.prototype to instance
  // 复制 Axios.prototype 到实例上。
  // 也就是为什么 有 axios.get 等别名方法,
  // 且调用的是 Axios.prototype.get 等别名方法。
  utils.extend(instance, Axios.prototype, context);
  // Copy context to instance
  // 复制 context 到 intance 实例
  // 也就是为什么默认配置 axios.defaults 和拦截器  axios.interceptors 可以使用的原因
  // 其实是new Axios().defaults 和 new Axios().interceptors
  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 class 允许 class 继承 也就是可以 new axios.Axios()
// 但  axios 文档中 并没有提到这个,我们平时也用得少。
axios.Axios = Axios;

// Factory for creating new instances
// 工厂模式 创建新的实例 用户可以自定义一些参数
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

工具函数方法之bind

`axios/lib/helpers/bind.js

'use strict';
// 返回一个新的函数 wrap
module.exports = function bind(fn, thisArg) {
  return function wrap() {
    var args = new Array(arguments.length);
    for (var i = 0; i < args.length; i++) {
      args[i] = arguments[i];
    }
    // 把 argument 对象放在数组 args 里
    return fn.apply(thisArg, args);
  };
};

传递两个参数和thisArg指向,把参数arguments生成数组,最后调用返回参数结构

工具函数之extend

extendb里面的属性和方法继承给a,并且将b里面的方法的执行上下文都绑定到thisArg

function extend(a, b, thisArg) {
  forEach(b, function assignValue(val, key) {
    if (thisArg && typeof val === 'function') {
      a[key] = bind(val, thisArg);
    } else {
      a[key] = val;
    }
  });
  return a;
}

工具函数之自定义forEach

自定义forEach方法遍历基本数据,数组,对象

/**
 * @param {Object|Array} obj The object to iterate
 * @param {Function} fn The callback to invoke for each item
 */
function forEach(obj, fn) {
  // Don't bother if no value provided
  // 判断 null 和 undefined 直接返回
  if (obj === null || typeof obj === 'undefined') {
    return;
  }

  // Force an array if not already something iterable
  // 如果不是对象,放在数组里。
  if (typeof obj !== 'object') {
    /*eslint no-param-reassign:0*/
    obj = [obj];
  }

  // 是数组 则用for 循环,调用 fn 函数。参数类似 Array.prototype.forEach 的前三个参数。
  if (isArray(obj)) {
    // Iterate over array values
    for (var i = 0, l = obj.length; i < l; i++) {
      fn.call(null, obj[i], i, obj);
    }
  } else {
    // Iterate over object keys
    // 用 for in 遍历对象,但 for in 会遍历原型链上可遍历的属性。
    // 所以用 hasOwnProperty 来过滤自身属性了。
    // 其实也可以用Object.keys来遍历,它不遍历原型链上可遍历的属性。
    for (var key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        fn.call(null, obj[key], key, obj);
      }
    }
  }
}

第三部分

取消相关API,还有allspread、导出等实现

// Expose Cancel & CancelToken
// 导出 Cancel 和 CancelToken
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');

// Expose all/spread
// 导出 all 和 spread API
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
// 也就是可以以下方式引入
// import axios from 'axios';
module.exports.default = axios;

spread方法

  • 假设你有这样的需求:
         function f(x,y,z){}
         var args = [1,2,3]
         f.apply(null,args)
  • 那么就可以用spread方法
axios.spread(function(x, y, z) {})([1, 2, 3]);
  • 实现源码:
/**
 * @param {Function} callback
 * @returns {Function}
 */
module.exports = function spread(callback) {
  return function wrap(arr) {
    return callback.apply(null, arr);
  };
};

核心构造函数Axios

axios/lib/core/Axios.js

function Axios(instanceConfig) {
 // 默认参数
 this.defaults = instanceConfig;
 // 拦截器 请求和响应拦截器
 this.interceptors = {
   request: new InterceptorManager(),
   response: new InterceptorManager()
 };
}
Axios.prototype.request = function(config){
  // 省略code
  // code ...
  var promise = Promise.resolve(config);
  // code ...
  return promise;
}
// 这是获取 Uri 的函数,这里省略
Axios.prototype.getUri = function(){}
// 提供一些请求方法的别名
// Provide aliases for supported request methods
// 遍历执行
// 也就是为啥我们可以 axios.get 等别名的方式调用,而且调用的是 Axios.prototype.request 方法
// 这个也在上面的 axios 结构图上有所体现。
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;

接下来看拦截器部分

拦截器管理构造函数interceptorManager

源码实现: 构造含糊,handles用于存储拦截器函数

    function InterceptorManager(){
        this.handles = []
     }

接下来声明了三个方法:使用、移除、遍历

 InterceptorManager.prototype.use 使用

传递两个函数作为参数,数组中的一项存储的是{fulfilled: function(){}, rejected: function(){}}。返回数字 ID,用于移除拦截器。

/**js
 * @param {Function} fulfilled The function to handle `then` for a `Promise`
 * @param {Function} rejected The function to handle `reject` for a `Promise`
 *
 * @return {Number} 返回ID 是为了用 eject 移除
 */
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

InterceptorManager.prototype.eject 移除

根据 use 返回的 ID 移除 拦截器。

/**
* @param {Number} id The ID that was returned by `use`
*/
InterceptorManager.prototype.eject = function eject(id) {
 if (this.handlers[id]) {
   this.handlers[id] = null;
 }
};

有点类似定时器setTimeout 和 setInterval,返回值是id。用clearTimeout 和clearInterval来清除定时器。

// 提一下 定时器回调函数是可以传参的,返回值 timer 是数字
var timer = setInterval((name) => {
  console.log(name);
}, 1000, 'aky');
console.log(timer); // 数字 ID
// 在控制台等会再输入执行这句,定时器就被清除了
clearInterval(timer);

 InterceptorManager.prototype.forEach 遍历

/**
 * @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);
    }
  });
};

实例结合

axios(options)
.then((res)=> {
  response.innerHTML = JSON.stringify(res.data, null, 2);
})
.catch(function (res) {
  response.innerHTML = JSON.stringify(res.data, null, 2);
});

先看调用栈流程

  1. send Request按钮点击submit.onclcik
  2. 调用axios函数实际上调用axios.prototype.request函数,而这个函数使用bind返回一个名为warp的函数
  3. 调用Axios.prototype.request
  4. 有请求拦截器的情况下执行拦截器请求,中间会执行dispatchRequest方法 5.dispatchRequest方法之后调用adapter (xhrAdapter) 6.1. 最后调用 Promise 中的函数dispatchXhrRequest,(有响应拦截器的情况下最后会再调用响应拦截器)

Axios.prototype.request请求核心方法

这个函数是核心函数。 主要做了这几件事:

1.判断第一个参数是字符串,则设置 url,也就是支持axios('example/url', [, config]),也支持axios({})。 2.合并默认参数和用户传递的参数 3.设置请求的方法,默认是是get方法 4.将用户设置的请求和响应拦截器、发送请求的dispatchRequest组成Promise链,最后返回还是Promise实例。 也就是保证了请求前拦截器先执行,然后发送请求,再响应拦截器执行这样的顺序。 也就是为啥最后还是可以thencatch方法的缘故。

Axios.prototype.request = function request(config) {
  /*eslint no-param-reassign:0*/
  // Allow for axios('example/url'[, config]) a la fetch API
  // 这一段代码 其实就是 使 axios('example/url', [, config])
  // 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
  // 组成`Promise`链 这段拆开到后文再讲述
};

组成Promise链,返回Promise实例

这部分:用户设置的请求和响应拦截器、发送请求的dispatchRequest组成Promise链。也就是保证了请求前拦截器先执行,然后发送请求,再响应拦截器执行这样的顺序 也就是保证了请求前拦截器先执行,然后发送请求,再响应拦截器执行这样的顺序 也就是为啥最后还是可以thencatch方法的缘故。

+=====================================未完待续====================================