axios源码分析

项目主要技术栈是Vue,因此咱们在请求后端接口的时候通常会用到axios的组件,那么咱们就来分析一下它的源码及它的实现。javascript

axios主要目录结构

├── /dist/                     # 项目输出目录
├── /lib/                      # 项目源码目录
│ ├── /cancel/                 # 定义取消功能
│ ├── /core/                   # 一些核心功能
│ │ ├── Axios.js               # axios的核心主类---------------------------这是其最核心部分
│ │ ├── dispatchRequest.js     # 用来调用http请求适配器方法发送请求         |
│ │ ├── InterceptorManager.js  # 拦截器构造函数                            |
│ │ └── settle.js              # 根据http响应状态,改变Promise的状态--------
│ ├── /helpers/                # 一些辅助方法
│ ├── /adapters/               # 定义请求的适配器 xhr、http----这个文件夹封装了ajax的请求
│ │ ├── http.js                # 实现http适配器
│ │ └── xhr.js                 # 实现xhr适配器
│ ├── axios.js                 # 对外暴露接口
│ ├── defaults.js              # 默认配置 
│ └── utils.js                 # 公用工具
├── package.json               # 项目信息
├── index.d.ts                 # 配置TypeScript的声明文件
└── index.js                   # 入口文件

由使用到探索

咱们通常使用的时候都会先new一个axios实例出来,那么这个实例是怎么来的呢?里边都有什么呢?咱们来看一下源代码最外层一个axios.js文件java

function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);
  var instance = bind(Axios.prototype.request, context);

  // Copy axios.prototype to instance
  utils.extend(instance, Axios.prototype, context);

  // Copy context to 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;

// Factory for creating new instances
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

经过源代码咱们能够看到axios导出的axios对象中有一个create方法,还有个Axios对象,同时axios自己仍是createInstance函数,而且他们都会传递一个参数进去,因此咱们在使用axios的时候会有多种不一样的调用方法,他们最终实例化的实际上是core文件下的Axios.js中所写的Axios构造函数。ios

// /lib/core/Axios.js
function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

Axios.prototype.request = function request(config) {
  // ...省略代码
};

// 为支持的请求方法提供别名
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

上边是部分源代码,咱们能够看到在这个构造函数中他会有个default对象,是用来存放咱们的配置信息的,还有个interceptors对象,里边包了两个实例,分别对应了request拦截器和response拦截器。而且在Axios的原型链上有写了一个request方法,并对支持的请求方法都赋值了一个别名。ajax

咱们的配置config是如何生效的

咱们看源代码会发现几乎每一个方法都会有一个config参数,咱们使用的时候的config就是经过这种方式贯穿整个axios的。咱们来看一下咱们使用的3种方式:json

import axios from 'axios'

// 第1种:直接修改Axios实例上defaults属性,主要用来设置通用配置
axios.defaults[configName] = value;

// 第2种:发起请求时最终会调用Axios.prototype.request方法,而后传入配置项,主要用来设置“个例”配置
axios({
  url,
  method,
  headers,
});

// 第3种:新建一个Axios实例,传入配置项,此处设置的是通用配置
let newAxiosInstance = axios.create({
  [configName]: value,
});

对比咱们刚才所说的axios实例的生成,咱们能够看到这三种方式其实都会把config用过参数的形式传递进去,而且在每一步调用的时候生效。axios

请求拦截器和响应拦截器

咱们在上边看axios的构造函数的时候看到他绑定了一个request拦截器和response拦截器,那么他们是作什么的呢?又是如何生效的呢?顾名思义,拦截器就是拦截请求的,能够在发送request以前或者接收response以前进行拦截,而后作一些事情达到咱们的目的。后端

// /lib/core/InterceptorManager.js

function InterceptorManager() {
  this.handlers = []; // 存放拦截器方法,数组内每一项都是有两个属性的对象,两个属性分别对应成功和失败后执行的函数。
}

// 往拦截器里添加拦截方法
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

// 用来注销指定的拦截器
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};

// 遍历this.handlers,并将this.handlers里的每一项做为参数传给fn执行
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};
// /lib/core/Axios.js
Axios.prototype.request = function request(config) {
  // ...
  var chain = [dispatchRequest, undefined];

  // 初始化一个promise对象,状态微resolved,接收到的参数微config对象
  var promise = Promise.resolve(config);

  // 注意:interceptor.fulfilled 或 interceptor.rejected 是可能为undefined
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  // 添加了拦截器后的chain数组大概会是这样的:
  // [
  //   requestFulfilledFn, requestRejectedFn, ..., 
  //   dispatchRequest, undefined,
  //   responseFulfilledFn, responseRejectedFn, ....,
  // ]

  // 只要chain数组长度不为0,就一直执行while循环
  while (chain.length) {
    // 数组的 shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。
    // 每次执行while循环,从chain数组里按序取出两项,并分别做为promise.then方法的第一个和第二个参数

    // 按照咱们使用InterceptorManager.prototype.use添加拦截器的规则,正好每次添加的就是咱们经过InterceptorManager.prototype.use方法添加的成功和失败回调

    // 经过InterceptorManager.prototype.use往拦截器数组里添加拦截器时使用的数组的push方法,
    // 对于请求拦截器,从拦截器数组按序读到后是经过unshift方法往chain数组数里添加的,又经过shift方法从chain数组里取出的,因此得出结论:对于请求拦截器,先添加的拦截器会后执行
    // 对于响应拦截器,从拦截器数组按序读到后是经过push方法往chain数组里添加的,又经过shift方法从chain数组里取出的,因此得出结论:对于响应拦截器,添加的拦截器先执行

    // 第一个请求拦截器的fulfilled函数会接收到promise对象初始化时传入的config对象,而请求拦截器又规定用户写的fulfilled函数必须返回一个config对象,因此经过promise实现链式调用时,每一个请求拦截器的fulfilled函数都会接收到一个config对象

    // 第一个响应拦截器的fulfilled函数会接受到dispatchRequest(也就是咱们的请求方法)请求到的数据(也就是response对象),而响应拦截器又规定用户写的fulfilled函数必须返回一个response对象,因此经过promise实现链式调用时,每一个响应拦截器的fulfilled函数都会接收到一个response对象

    // 任何一个拦截器的抛出的错误,都会被下一个拦截器的rejected函数收到,因此dispatchRequest抛出的错误才会被响应拦截器接收到。

    // 由于axios是经过promise实现的链式调用,因此咱们能够在拦截器里进行异步操做,而拦截器的执行顺序仍是会按照咱们上面说的顺序执行,也就是 dispatchRequest 方法必定会等待全部的请求拦截器执行完后再开始执行,响应拦截器必定会等待 dispatchRequest 执行完后再开始执行。

    promise = promise.then(chain.shift(), chain.shift());

  }

  return promise;
};

dispatchrequest作了什么事情

咱们再看拦截器生效的时候发如今Axios.prototype.request 中会有这么一段代码var chain = [dispatchRequest, undefined];,而且会在promise = promise.then(chain.shift(), chain.shift());被调用,那么这个dispatchrequest是什么呢?数组

// /lib/core/dispatchRequest.js
module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config);

  // Support baseURL config
  if (config.baseURL && !isAbsoluteURL(config.url)) {
    config.url = combineURLs(config.baseURL, config.url);
  }

  // Ensure headers exist
  config.headers = config.headers || {};

  // 对请求data进行转换
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );

  // 对header进行合并处理
  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers || {}
  );

  // 删除header属性里无用的属性
  utils.forEach(
    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
    function cleanHeaderConfig(method) {
      delete config.headers[method];
    }
  );

  // http请求适配器会优先使用config上自定义的适配器,没有配置时才会使用默认的XHR或http适配器,不过大部分时候,axios提供的默认适配器是可以知足咱们的
  var adapter = config.adapter || defaults.adapter;

  return adapter(config).then(/**/);
};

经过源码分析,咱们能够看到dispatchrequest主要作了3件事情:promise

  • 拿到config对象,对config进行传给http请求适配器前的最后处理;异步

  • http请求适配器根据config配置,发起请求 ;

  • http请求适配器请求完成后,若是成功则根据header、data、和config.transformResponse拿到数据,

转换后的response,并return。

段落小结

经过上面对axios源码结构分析,咱们能够获得axios搭建的一个主要结构:

调用===>Axios.prototype.request===>请求拦截器interceptors===>dispatchRequest===>请求转换器transformRequest===>请求适配器xhrAdapter===>响应转换器transformResponse===>响应拦截器interceptors

axios如何基于promise搭建异步桥梁

了解了axios的主要结构及调用顺序,那咱们来看一下axios是如何经过promise来搭建异步的桥梁?

axios.get(/**/)
.then(data => {
  // 此处能够拿到向服务端请求回的数据
});
.catch(error => {
  // 此处能够拿到请求失败或取消或其余处理失败的错误对象
});

咱们那get方法来举例,当咱们调用axios的get方法的时候其实是调用axios原型链上的get方法,而其原型链上的get方法又指向了其原型链上的request方法。

utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});
Axios.prototype.request = function request(config) {
  // ...
  var chain = [dispatchRequest, undefined];
  // 将config对象看成参数传给Primise.resolve方法
  var promise = Promise.resolve(config);

  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

咱们能够看到在request方法里边有个chain数组,这个实际上是至关于一个方法的队列,这里会把request的拦截器插入chain数组的前边,response拦截器插入chain数组后边,经过下边的while循环,两两调用promise.then来顺序执行请求拦截器到dispatchrequest再到响应拦截器的方法。

在dispatchrequest中呢又会调用封装好的ajax(xhrAdapter方法),xhrAdapter方法返回的是还一个Promise对象

// /lib/adapters/xhr.js
function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    // ... 省略代码
  });
};

xhrAdapter内的XHR发送请求成功后会执行这个Promise对象的resolve方法,并将请求的数据传出去, 反之则执行reject方法,并将错误信息做为参数传出去。

相关文章
相关标签/搜索