一篇文章带你深度解析 axios 源码

3 篇文章 0 订阅
2 篇文章 1 订阅

axios深度解析(源码)

axios的作用

支持请求/响应拦截器

支持取消请求

请求/响应数据转换

批量发送多个请求

axios基本使用

npm install axios 该文件 在axios目录下的lib下的axios.js

博客的目的通过对源码的解析来完成对axios的执行流程有一个深入的了解。

先说一下axios常用语法

  • axios(config):通用/最本质的发任意类型请求的方式
  • axios(ur1[, config]): 可以只指定ur1发get请求
  • axios.request(config):等同于axios(config)
  • axios.get(ur1[, config]): 发get请求
  • axios.delete(url[, config]): 发delete请 求
  • axios.post(ur1[, data, config]): 发post请求
  • axios.put(url[, data, config]): 发put请求
  • axios.defaults . xxx:请求的默认全局配置
  • axios.interceptors.request.use():添加请求拦截器
  • axios.interceptors.response.use():添加响应拦截器
  • axios.create( [config]):创建个 新的axios(它没有 下面的功能)
  • axios.Cancel():用于创建取消请求的错误对象
  • axios.CancelToken():用于创建取消请求的token对象
  • axios.isCancel():是否是一个取消请求的错误
  • axios.all(promises):用于批量执行多个异步请求
  • axios.spread():用来指定接收所有成功数据的回调函数的方法

其实上面这一些用法有一个核心的代码就是request机制,理解了源码中的request机制会对axios有更全面的认知。

axios入口文件核心代码

首先我们先npm install axios下载下来这个包,然后进入axios目录下的lib中的axios.js文件来看下面的核心代码。

axios入口文件

var utils = require('./utils'); //下面代码中拷贝的模块
var bind = require('./helpers/bind'); // bind模块 修改this
var Axios = require('./core/Axios'); //Axios核心模块
var mergeConfig = require('./core/mergeConfig'); // 合并模块
var defaults = require('./defaults'); //默认配置模块 
//这个defaults默认有很多属性,这些属性就是在defaults.js中可以找到

首先 它引入 了许多东西,分别是一些功能函数和一些配置,这里重点关注一下Axios和defaults模块。

// axios和axios.create()直接调用这个函数
function createInstance(defaultConfig) {
  //创建一个Axios的实例,传入这个配置对象,这个Axios由于是Axios模块中,则会在下面代码中展示出来并分析。
  var context = new Axios(defaultConfig);
    
  //等同于Axios.prototype.request.bind(context) 
  //把this指定成Axios的实例就能使用Axios上的属性了,使内部使用的this正确。
  var instance = bind(Axios.prototype.request, context);

  // Copy axios.prototype to instance   
  //扩展 将第二个Axios原型上的所有东西都拷贝到instance上 如: Axios.prototype.geturi ,Axios.prototype.get 等方法
  utils.extend(instance, Axios.prototype, context);

  // Copy context to instance 又把实例上的所有东西copy到instance上, 实例上有defaults和interceptors,代码在下面Axios核心模块中
  utils.extend(instance, context);
 
  return instance;
  //经过这一大通的拷贝 他的作用是什么呢? 为什么不直接使用实例返回呢? 因为这样的话他就不能axios()直接这样加括号执行了。
  // 所以这个函数的返回值本质上就是 Axios.prototype.request, 这个会在下面详细讲解这个request
}

//我们的axios直接调用的是这个createInstance函数
var axios = createInstance(defaults);

// Expose Axios class to allow class inheritance
axios.Axios = Axios;
// Factory for creating new instances
axios.create = function create(instanceConfig) {
  //这个传入的参数 mergeConfig 拿着axios默认配置和 一个新的配置(instanceConfig)合并成一个新的配置
  //这个就是为什么有多个后台接口就可以使用 这个方法 他这个配置是独立的
  //和axios的区别 create这个没有后面添加的cancel/cancelToken等
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
// axios和axios.create的功能基本一致 由此可见调用axios和使用create方法创建本质上都是调用的createInstance方法
// Expose Cancel & CancelToken 下面这些属性都是 instance 没有的。
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;

Axios核心模块

Axios构造函数

//这里只写出了 Axios构造函数 详细内容在 下面request中
function Axios(instanceConfig) {
  //把配置对象添加到 这个defaults属性上
  this.defaults = instanceConfig;
  //将包含请求/响应拦截器 的对象保存到interceptors上
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}
//中间我省略了中间代码,由于这个函数尤其重要 我放到下面interceptors中单独讲解。
Axios.prototype.request = function request(config){
    //此间省略大量代码
}



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

// 下面代码把这个发请求的方法都绑定到Axios.prototype上
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
    }));
  };
});

defaults模块:

如下部分代码可以看出为什么defaults中为什么会有那么多的默认配置。让然还有很多属性没有列出,有兴趣可以自己看一下。

utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {
  defaults.headers[method] = {};
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});

request和 interceptors (拦截器)

Axios核心模块

request整体流程 前面的话都是一些配置的处理,可以不用太关注

// axios()和axios.request()这两种写法是一样的,这些方法目前还在Axios的原型上
Axios.prototype.request = function request(config) {
  //用于发请求的函数
  /*eslint no-param-reassign:0*/
  // Allow for axios('example/url'[, config]) a la fetch API
  if (typeof config === 'string') {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  }

  config = mergeConfig(this.defaults, config);

  // Set config.method
  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  下面是重点 (圈起来 要考!)
  // 有一个数组数组的左边放请求拦截器,数组的右边放响应拦截器,中间这个dispatchRequest是用来发请求的函数,详细分析如 图一:
  var chain = [dispatchRequest, undefined];
  //成功的promise 值就是这个config
  var promise = Promise.resolve(config);
  //unshift
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });
  // push
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  //  
  while (chain.length) {
      //重点 (圈起来 要考) 这一步的作用就是 在图二中把 数组的第一项,和第二项(拦截器成功和失败回调) 取出 ,作为这个promise成功的回调和失败的回调,然后循环遍历这个数组,取出所有的请求拦截器
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

图一:

在这里插入图片描述

有多个请求拦截器, 请求拦截器执行和 添加请求拦截器的顺序是相反的,就比如你添加了两个请求拦截器,后添加的那一个先执行,请求拦截器 执行完之后 ,就会发请求,然后会有响应结果,有了响应结果后 执行响应拦截器,执行完响应拦截器 然后 就是执行 成功和失败的回调 .then()方法。

图二:

在这里插入图片描述

能坚持到这里的小伙伴 已经很不容易了 可能有小伙伴不太理解这个 dispatch Request 怎么发的请求 ,同样我们进入这个模块–撸源码。

dispatchRequest模块和adapters

module.exports = function dispatchRequest(config) {
      // Transform request data 数据的转换  json处理
    config.data = transformData(
         config.data,
         config.headers,
   		 config.transformRequest
    );
    //省略很多代码
    var adapter = config.adapter || defaults.adapter; 
    
    return adapter(config).then(function onAdapterResolution(response) {
        //省略很多代码
        //它返回的也是一个promise ,我们需要知道这个adapter 最终找的是谁?
    }
}

我们需要知道这个adapter 最终找的是谁?在lib下有一个为adapters的目录 其中有http.js和xhr.js.我们发的ajax用的就是这个xhr模块 ,在这个模块中 有一个xhrAdapter(config) 方法,返回的也是一个promise ,发请求的具体方法 就在这个函数内部,源码就不贴了 (有兴趣的小伙伴可以看一下)。

现在我总结一下这个流程 request(config) —> dispathRequest(config) —> xhrAdapter(config)

request(config) :|将请求拦截器/ dispatchRequest() /响应拦截器通过promise链串连起来,返回promise
dispatchRequest(config):转换请求数据 —> 调用xhrAdapter()发请求 —>请求返回后转换响应数据。返回promise
xhrAdapter(config):创建XHR对象,根据config进行相应设置,发送特定请求,并接收响应数据,返回promise

文章最后有这个流程图

axios取消请求cancel

lib下有一个cancel目录,里面有三个文件分别为 Cancel.js,CancelToken.js,isCancel.js

Cancel模块

//构造函数 也是一个error
function Cancel(message) {
  this.message = message;
}

Cancel.prototype.toString = function toString() {
  return 'Cancel' + (this.message ? ': ' + this.message : '');
};

Cancel.prototype.__CANCEL__ = true;

module.exports = Cancel;

isCancel模块

//判断 是否是这个Cancel 还是一个一般的error
module.exports = function isCancel(value) {
  return !!(value && value.__CANCEL__);
};

CancelToken模块

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;
    }
    //Cancel模块 
    token.reason = new Cancel(message);
    //将取消请求的promise 指定为成功 值为reason 
    resolvePromise(token.reason); 
    //之后就会执行xhr.js模块的中的config.cancelToken.promise.then()
  });

xhr.js

//xhr中的 有关中断请求的部分代码
config.cancelToken.promise.then(function onCanceled(cancel) {
    if (!request) {
        return;
    }
	// 中断
    request.abort();
    //promise进入失败 传入这个cancel对象 这个就是 error 
    reject(cancel);
    // Clean up request
    request = null;
});

整体流程图

为什么把流程图放最后而不是放前面,因为这个图 不了解源码看了跟没看一样。
在这里插入图片描述

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值