Axios是前端使用的比较广泛的一个js库。以前一直是只知道使用,但是对于其内部实现一直不太了解,现在花点时间好好来研究源码,了解一下其中的实现过程

​ 对于使用过Axios的人应该都知道,它既可以作为函数使用,也可以作为一个对象使用。

作为函数使用

const axiosInstance=axios("/api/gatdata",options);//options是一个可选参数对象
axiosInstance.then(res=>{},err=>{})
1
2

作为对象使用

const axiosInstance=axios.craete({options});
axiosInstance({url:"/api/getdata"}).then(res=>{},err=>{});//params是可选对象
1
2

​ 我们看一下axios (opens new window)官方库里的lib\axios.js就可以知道。通过module.exports=axios导出的是一个函数对象。同时在该函数对象上添加了Axioscreate属性。通过axios.create()方法返回一个函数对象。所以axios()和axios.create()实际上调用的是同一个函数——createInstance()

接下里我们将从入口文件axios\lib\axios.js文件开始分析。

# 1、入口文件——axios\lib\axios.js

​ 文件地址为axios (opens new window),大家可以自行去 github (opens new window)上看,也可以clone到本地仔细研究。

1、创建axios实例——createInstance()

function createInstance(defaultConfig) {
  // 创建实例
  var context = new Axios(defaultConfig);
  // 返回 调用Axios.prototype.request,参数为context的新方法
  // instance是一个返回promise的新方法
  var instance = bind(Axios.prototype.request, context);
  // Copy axios.prototype to instance
  // 将 Axios.prototype添加到instance上面
  utils.extend(instance, Axios.prototype, context);
  // Copy context to instance
  utils.extend(instance, context);
  return instance;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

​ 该方法主要分为以下几步执行:

  • 第1步,创建一个Axios类的实例对象。

    var context=new Axios(defaultConfig)
    
    1
    • 第2步,调用bind方法,将Axios.prototype.request作为原函数,Axios实例对象作为this对象传入原函数,并返回一个新函数。后面会重点来讲述Axios.prototype.request函数。
     var instance = bind(Axios.prototype.request, context);
    
    1
  • 第3步,将Axios.prototype对象和context对象上的属性都添加到instance上

    //将Axios原型对象上的属性添加到instance实例对象上。添加`context`的目的是为了在调用`Axios.prototype`上面的方法时,保证方面里面的`this`对象指向`context`对象。
    utils.extend(instance, Axios.prototype, context);
    // 将context实例对象的私有属性(Axios自身的属性)添加到instance对象上。
    utils.extend(instance, context);
    
    1
    2
    3
    4
  • 第4步,返回instance实例对象。

2、创建axios对象并导出该对象

​ 通过调用createInstance方法创建axios对象,并给该对象添加Axios,create,canCel等属性。并将axios对象以es6(module.exports.default = axios;)commonjs(module.exports = axios;)的语法分别导出。

// 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));
};
// 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');
//require("axios")
module.exports = axios;
// Allow use of default import syntax in TypeScript
// import axios from 'axios';
module.exports.default = axios;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 2、创建axios实例的文件——axios\core\Axios.js

​ 文件地址为Axios (opens new window)。接下来我们来看看lib\core\Axios.js文件里初始化Axios实例的过程。

1、首先是Axios构造函数的定义

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
      request: new InterceptorManager(),
      response: new InterceptorManager()
  };
}
1
2
3
4
5
6
7

2、原型方法——Axios.prototype.request

​ 其实我们在创建axios实例过程中,最终调用的方法还是是Axios.prototype.request方法。接下来我们来好好看一下该方法的实现。

  • 第1步,先判断传入的配置文件是字符串还是其他类型(对象类型)。并将默认配置文件和传入的配置文件合并。

    if (typeof config === 'string') {
        config = arguments[1] || {};
        config.url = arguments[0];
    } else {
        config = config || {};
    }
    // 合并配置
    config = mergeConfig(this.defaults, config);
    
    1
    2
    3
    4
    5
    6
    7
    8
  • 第2步,判断axios向后台请求数据的方式,并将请求方式都转换成小写。默认请求方式是get方式。

    if (config.method) {
        config.method = config.method.toLowerCase();
    } else if (this.defaults.method) {
        config.method = this.defaults.method.toLowerCase();
    } else {
        config.method = 'get';
    }
    
    1
    2
    3
    4
    5
    6
    7
  • 第3步。这里面的核心方法是dispatchRequest,该方法是用于和后台交互用的,在下文会详细说明。

    • 首先新建一个数组(chain),用于存放promise实例的then方法。

    • 其次创建一个 promise实例,目的是为了能够链式处理请求拦截和响应拦截。

    • 然后遍历请求拦截数组,并将请求拦截then方法(包括resolve方法和reject方法) 添加到chain数组的最前面;遍历响应拦截数组,并将响应拦截then方法(包括resolve方法和reject方法) 添加到chain数组的最后面。

    • 最后使用while循环。首先对请求拦截的成功和失败情况进行处理。然后向后台请求数据。最后对响应数据进行预处理。

      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);
      });
      
      while (chain.length) {
          promise = promise.then(chain.shift(), chain.shift());
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
  • 第4步,将经过响应拦截后的promise实例返回。通过调用then方法就能对后台返回的数据(在this.interceptors.response.use会先进行预处理)进行处理。这也就是为什么我们调用axios()axios.create()方法返回的是一个promise实例对象了。

3、原型方法——Axios.prototype.getUri

​ 处理传入的params,构造后台需要的URl

Axios.prototype.getUri = function getUri(config) {
  config = mergeConfig(this.defaults, config);
  return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');
};
1
2
3
4

4、原型方法——Axios.prototype.get/post

​ 这也就是为什么可以直接axios.get()、axios.post()等方法的原因。但其实本质上调用getpost方法时,调用的还是request方法。相当于给request方法设置了别名一样。

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
    }));
  };
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 3、拦截器文件——lib\core\InterceptorManager.js

​ 主要是通过在初始化axios时,调用以下代码来对请求和响应进行拦截处理的。文件地址为InterceptorManager (opens new window)。该拦截器在如下代码中才会起作用。

const instance=axios.create();
//设置请求拦截
instance.interceptors.request.use(config=>{},err=>{})//设置响应拦截
instance.interceptors.response.use(res=>{},err=>{})
1
2
3
4
5

下面我们来看一下InterceptorManager类的实现过程。该类中主要有以下添加拦截(use),取消拦截(reject)和

1、原型方法——InterceptorManager.prototype.use

​ 该方法的主要作用是将拦截操作添加到数组中。

// Add a new interceptor to the stack
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};
1
2
3
4
5
6
7
8

2、原型方法——InterceptorManager.prototype.reject

​ 该方法的主要作用是将拦截操作从拦截数组中删除。

//Remove an interceptor from the stack
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};
1
2
3
4
5
6

3、原型方法——InterceptorManager.prototype.forEach

​ 遍历拦截数组,进行拦截操作。

//Iterate over all the registered interceptors
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};
1
2
3
4
5
6
7
8

# 4、后台请求操作文件——lib\core\dispatchRequest.js

​ 该方法主要用于向后台请求数据。

  • 如果已经取消,则 throw 原因报错,使Promise走向rejected
  • 确保 config.header 存在。
  • 利用用户设置的和默认的请求转换器转换数据。
  • 拍平 config.header
  • 删除一些 config.header
  • 返回适配器adapterPromise实例)执行后 then执行后的 Promise实例。返回结果传递给响应拦截器处理。

该文件中的核心代码如下所示。

// Dispatch a request to the server using the configured adapter.
module.exports = function dispatchRequest(config) {
  ...//为了更好的布局,将一些代码省略了
  var adapter = config.adapter || defaults.adapter;
  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);
  });
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 5、请求取消操作文件

​ 取消操作的文件主要存在于lib\cancel文件夹。

1、判断是否取消

​ 该文件地址为isCancel (opens new window),通过调用axios.isCancel(new Cancel())来决定是否取消操作。

module.exports = function isCancel(value) {
  return !!(value && value.__CANCEL__);
};
1
2
3

2、在取消时给用户的提示信息

​ 该文件地址为Cancel (opens new window),在调动该函数时传递提示信息,并重写toString方法。

function Cancel(message) {
  this.message = message;
}
Cancel.prototype.toString = function toString() {
  return 'Cancel' + (this.message ? ': ' + this.message : '');
};
Cancel.prototype.__CANCEL__ = true;
1
2
3
4
5
6
7

3、取消操作的逻辑处理

var Cancel = require('./Cancel');
/**
 * A `CancelToken` is an object that can be used to request cancellation of an operation.
 */
function CancelToken(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  }

  var resolvePromise;
  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);
  });
}
/**
 * Throws a `Cancel` if cancellation has been requested.
 */
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
  if (this.reason) {
    throw this.reason;
  }
};
/**
 * Returns an object that contains a new `CancelToken` and a function that, when called,
 * cancels the `CancelToken`.
 */
CancelToken.source = function source() {
  var cancel;//is a function
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  return {
    token: token,
    cancel: cancel
  };
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Last Updated: 6/4/2020, 10:31:46 AM