上篇文章《axios中的参数为啥没被完全编码》里我们探讨了axios的一个小的知识点,这篇我们就接着来读读axios的源码,看看他究竟是如何实现的。

初识这个库时,第一反应,这个axios怎么读,万一读错了会不会别人笑;这样的疑问不仅我会有,后面在他的issues#802里还看到了一群人在一起讨论。高票赞同的读法是:acks--ee--oh-ss

用中文的相似音是: 哎克 C 欧斯  ,常见读的比较多的还有类似: 阿克休斯 

回归到文章的主题,如果让我们去写一个网络库,需要考虑哪些点:

1、支持哪些请求方式(get、post等)

2、请求配置(超时、编码)

3、支持异步、并发

4、拦截器(请求发出前拦截、请求返回后拦截)

5、取消请求

6、请求结果回调(正确、其他的)

带着这些问题,我们来一起来看看axios的源码如何做的。

首先参看下axios的目录结构:

├── adapters
│   ├── README.md
│   ├── http.js               #node环境http对象
│   └── xhr.js                #web环境http对象
├── axios.js                  #入口
├── cancel
│   ├── Cancel.js             #取消的构造对象类
│   ├── CancelToken.js        #取消操作的包装类
│   └── isCancel.js           #工具类
├── core
│   ├── Axios.js              #Axios实例对象
│   ├── InterceptorManager.js #拦截器控制器
│   ├── README.md
│   ├── buildFullPath.js      #拼接请求的url、baseURL
│   ├── createError.js        #创建异常信息类工具
│   ├── dispatchRequest.js    #默认的拦截器(分发完全请求的拦截器)
│   ├── enhanceError.js       #异常信息类实体
│   ├── mergeConfig.js        #合并配置文件(用户设置的和默认的)
│   ├── settle.js             #根据http-code值来resolve/reject状态
│   └── transformData.js      #转换请求或相应的数据的工具类
├── defaults.js               #默认配置类
├── helpers/                  #一些辅助方法
└── utils.js                  #通用的工具类

各个类的主要功能,这里已经标注出来了,其核心模块主要有两个,一个是adapters 实际请求的发出的模块,另外一个就是core里面实现了一个请求从创建到完成的整个流程控制。adapters模块是对XMLHttpRequest的包装,这里不作过多的概述(其方法细节的实现),主要还是看下core里的做了哪些事情。

首先,我们要从源码的角度来看看一个请求是如何被发出的,下面以get请求发出到收到响应为例(axios.get)。

我们在项目使用axios时,通常是  import Axios from "axios" ,别看简单的这一句导包,他里面可做了好多事:

从axios.js这个入口文件,我们看到其创建了一个axios实例,并添加了一些方法。其关键方是在createInstance()

function createInstance(defaultConfig) {
  // 创建一个Axios实例,这里理解成包装了请求方法的一个对象
  var context = new Axios(defaultConfig);


  // 为request方法bind上Axios,一个包装的wrap方法:
  // 可以简单理解成创建了一个对象。之所以这样创建为了方便能 
  // axios('https://m.zz.cn/a')这样用
  var instance = bind(Axios.prototype.request, context);


  // 把 Axios.prototype 上的方法拓展到instance上
  utils.extend(instance, Axios.prototype, context);


  // 把context中的defaults、interceptors拓展到instance实例中
  utils.extend(instance, context);


  return instance;
}

导包成功后,接着调用请求方法,像这样(还有其他的方式):

axios
  .get(URL, {
    params: {
      zz: 'fe'
    }
  })
  .then(successFun, failureFun)
  .catch(erorFun);

调用的get方法其实是new Axios()中封装的方法,其最终会调到Axios.prototype.request指向的函数中(这才是所有请求方法调用的合并体):

Axios.prototype.request = function request(config) {
  ...
  // 合并配置参数
  config = mergeConfig(this.defaults, config);


  // 如果没指定请求方式,默认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
  // ★★★ 这一块是axios的核心部分 ★★★ 
  // 其做的事情是:通过Promise把整个请求、拦截器处理封装到一条链上,按顺序调用
  // 这其中就包括最重要的一环:实际请求发送者:dispatchRequest
  var chain = [dispatchRequest, undefined];
  var promise = Promise.resolve(config);


   // 添加用户拦截器到链上:请求前的
  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);
  });
  // 通过promise.then产生关联
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }
  
  return promise;
};

这里面巧妙之处创建了一个请求处理链 china,在通过了Promise.resolve()创建一个promise对象,然后通过promise.then()把请求链串起来。

这种设计在设计模式上叫做:责任链模式(Chain of Responsibility),允许你将发出的请求沿着处理者链进行发送, 收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。

上面这张网图比较形象的比喻责任链模式。

我们在回到axios上,看他这一条链是怎样的:

上面代码区里提到这个请求链中有个默认添加的重要的一环dispatchRequest,他会根据不同的平台(其实在new Axios()时已经确定使用哪个adapter了)来发出网络请求,并承担了对请求前后的数据包装工作。

function getDefaultAdapter() {
  var adapter;
  if (typeof XMLHttpRequest !== 'undefined') {
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
  } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // For node use HTTP adapter
    adapter = require('./adapters/http');
  }
  return adapter;
}

接下来就是对后续拦截器的调用了(链中dispatchRequest 之前的是RequestInterceptor,之后的叫ResponseInterceptor),最终promise会回调到业务发起请求的地方。

最后,我们回头再捋一下这整个请求过程,他大致长这样:

后续

得益于axios中adapter的设计,后续如果我们有其他的平台的网络请求(eg.小程序),我们只需要实现对应端请求的adapter,就可以像平常一样使用了,抹平了各端的差异。

责任链模式,这种设计模式在封装网络库的时候非常实用,在Android/Java上知名的一个请求库OkHttp,其也使用了责任链模式。大家以后有机会写网络库的时候也可以参考下。

这篇文章没有过多的去细说实现细节,只从整体上讲了其实现框架,后续大家可以顺着整体框架自己去细读源码。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐