阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

Axios源码解析

38次阅读
没有评论

共计 5522 个字符,预计需要花费 14 分钟才能阅读完成。

导读 宏观的事聊完了,下面就详细聊几个核心细节吧:整个流程、请求 / 响应拦截器、dispatchRequest 是个啥、请求 / 响应数据转换器。
一、领悟思想

Axios 是一个基于 Promise 的 HTTP 库,根据官网介绍,有以下几个特点:

    在浏览器端会创建 XMLHttpRequests
    在 Node 端会创建 HTTP 请求
    由于 Axios 是一个基于 Promise 的 HTTP 库,所以其支持 Promise API
    支持请求和响应拦截器
    支持请求和响应数据转换
    支持取消请求
    自动转换 JSON 数据
    客户端支持防御 XSRF 攻击

通过上述官网介绍的特点,我认为其突出的优点有三个:

    支持 Promise API,可以方便进行链式调用;
    支持请求和响应拦截器,该拦截器将 Node 中中间件思想引入该库,在请求发送之前和响应接收之后可以对其进行处理。
    支持数据转换器,转换器主要负责数据发送前以及响应接收后对数据的处理。
二、把握设计

理解了该库设计的特点,下面从源码目录、抽象接口及核心设计原理三个层面对 Axios 进行整体的把握。

2.1 源码目录

如下所示是 Axios 的源码目录及各个文件的作用

Axios 源码解析

2.2 抽象接口

对源码的目录有了一定了解,下面利用 UML 类图对该系统各个模块的依赖关系进一步了解,为后续源码分析打好基础。(看该图注意对着源码一起看)

Axios 源码解析

2.3 设计原理

首先看一段代码,这段代码的执行顺序包含着 Axios 的核心原理。

axios.defaults.baseURL = 'http://localhost:8080' 
 
// 请求拦截器一 
axios.interceptors.request.use( 
    config => {console.log('请求拦截器一', config); 
        return config; 
    }, 
    error => {console.log('request interceptor rejected1'); 
        return Promise.reject(error); 
    } 
); 
 
// 请求拦截器二 
axios.interceptors.request.use( 
    config => {console.log('请求拦截器二', config); 
        return config; 
    }, 
    error => {console.log('request interceptor rejected2'); 
        return Promise.reject(error); 
    } 
); 
 
// 响应拦截器一 
axios.interceptors.response.use( 
    response => {console.log('响应拦截器一', response); 
        return response; 
    }, 
    error => {console.log('response interceptor rejected1'); 
        return Promise.reject(error); 
    } 
); 
 
// 响应拦截器二 
axios.interceptors.response.use( 
    response => {console.log('响应拦截器二', response); 
        return response; 
    }, 
    error => {console.log('response interceptor rejected2'); 
        return Promise.reject(error); 
    } 
); 
 
axios('/', { 
    method: 'post', 
    headers: {'Content-Type': 'application/json'}, 
    data: {test: 'test'}, 
    // 请求转换器 
    transformRequest: [(data, headers) => {console.log('请求转换器', data); 
        return JSON.stringify(data) 
    }], 
    // 响应转换器 
    transformResponse: [(response, headers) => {console.log('响应转换器', response); 
        return response; 
    }] 
}) 
.then((response) => {console.log(response.data) 
})

写了这么多代码,大家肯定对这段代码的执行结果很感兴趣,为了满足各位看客的好奇心,下面就直接抛出来这段结果。

Axios 源码解析

不过单看执行结果也不能了解其核心设计原理呀,老铁别急,其实小小代码就已经包含了 Axios 的整个执行过程,通过观察结果及代码可以将整个过程简化为下图:

Axios 源码解析

其核心原理就是这个吗? 是的,你没有看错,这就是 Axios 的核心设计原理,通过一系列链式的处理就能够得到所需要的结果。

三、体会细节

宏观的事聊完了,下面就详细聊几个核心细节吧:整个流程、请求 / 响应拦截器、dispatchRequest 是个啥、请求 / 响应数据转换器。

3.1 整体运行流程

在第二章中阐述了该核心原理,老铁们一定对该整体是如何运转起来的很感兴趣吧,下面就来解答各位老铁的疑惑——Axios

function Axios(instanceConfig) { 
  this.defaults = instanceConfig; 
  // 拦截器实例化 
  this.interceptors = {request: new InterceptorManager(), 
    response: new InterceptorManager()}; 
} 
 
// 通过一系列的继承绑定操作,该函数其实就是 axios 函数 
Axios.prototype.request = function request(config) { 
  // …… 
  config = mergeConfig(this.defaults, config); 
 
  // Set config.method 
  // …… 
 
  // **** 核心 **** 
  // 存储该调用链的数组 
  var chain = [dispatchRequest, undefined]; 
  var promise = Promise.resolve(config); 
 
  // 将请求拦截器的内容塞到数组前面(注意用的 unshift 函数,这就很好的解释了为什么先调用的请求拦截器后执行)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 将整个数组中的内容串起来,这样就可以按照顺序链式执行了 
  while (chain.length) {promise = promise.then(chain.shift(), chain.shift()); 
  } 
 
  return promise; 
};

是不是很巧妙? 通过利用数组先来存储需要的内容,先处理的在数组的前面(请求拦截器),后处理的在数组的后面(响应拦截器),然后利用 Promise 将整个内容串起来,很好的处理网络请求属于异步的问题——Perfect。

3.2 请求 / 响应拦截器

通过观察第二部分的执行结果我们已经了解了请求 / 响应拦截器,下面就做一下总结:

    请求拦截器就是在发送请求前执行的回调函数,个人认为其最大功用就是对多个请求的配置进行统一修改
    仔细观察发现请求拦截器 1 先加入但是后执行,是不是与整体运行流程中的代码对上了。
    响应拦截器就是在请求得到响应后执行的回调函数,成功回调的参数就是响应 response,其可以对多个请求的响应进行统一修改。

先抛出请求 / 响应拦截器的核心代码

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; 
  } 
}; 
 
// 对拦截器进行分发 
InterceptorManager.prototype.forEach = function forEach(fn) {utils.forEach(this.handlers, function forEachHandler(h) {if (h !== null) {fn(h); 
    } 
  }); 
};

看看拦截器的核心源码,是不是发现与一种设计模式很像? 对的,就是观察者模式。当调用 use 方法的时候就会将回调函数 (成功、失败) 保存至 handlers 属性上,方便后期的调用; 当调用 eject 方法的时候就会删除对应索引位置回调函数; 当调用 forEach 方法的时候就会就会对 handlers 属性 (存储的拦截器回调) 中的内容进行分发。

3.3 dispatchRequest 是个啥

前面聊了整个请求的请求前 (请求拦截器) 和请求后(响应拦截器),是不是感觉少点东西,如何发请求,这就是我们本次要与大家一起唠的 dispatchRequest(config)。

module.exports = function dispatchRequest(config) { 
  // …… 
 
  // 请求数据转换 
  config.data = transformData( 
    config.data, 
    config.headers, 
    config.transformRequest 
  ); 
  // …… 
   
  // 获取适配器:自己配置了就选自己的,自己没有设置就选默认的(浏览器端就选 xhrAdapter、node 端就选 httpAdapter;这也就是为什么 Axios 即支持浏览器又支持 Node 的原因)var adapter = config.adapter || defaults.adapter; 
 
  return adapter(config).then(function onAdapterResolution(response) { 
    // …… 
 
    // 响应数据转换器 
    response.data = transformData( 
      response.data, 
      response.headers, 
      config.transformResponse 
    ); 
 
    return response; 
  }, function onAdapterRejection(reason) {if (!isCancel(reason)) { 
      // …… 
 
      // 响应数据转换器 
      if (reason && reason.response) { 
        reason.response.data = transformData( 
          reason.response.data, 
          reason.response.headers, 
          config.transformResponse 
        ); 
      } 
    } 
 
    return Promise.reject(reason); 
  }); 
};

通过观察整个请求流程中的中间环节——dispatchRequest,它一共做了三件事:

    调用请求数据转换器转换请求数据
    选择合适的适配器发起请求——自己配置了就选自己的,自己没有配置就选默认的(浏览器端就选 xhrAdapter、node 端就选 httpAdapter; 这也就是为什么 Axios 即支持浏览器又支持 Node 的原因)
    当请求数据返回后,调用响应数据转换器转换响应数据
3.4 请求 / 响应数据转换器

既然 3.3 中提到了请求 / 响应转换器,本节就来聊一聊它俩。

// 核心源码 
module.exports = function transformData(data, headers, fns) {utils.forEach(fns, function transform(fn) {data = fn(data, headers); 
  }); 
 
  return data; 
};

请求数据转换调用,实质上就是利用请求数据转换器对请求头和请求数据进行特定的处理(transformRequest 为处理函数的数组,defaults 中包含默认的配置)

config.data = transformData( 
  config.data, 
  config.headers, 
  config.transformRequest 
);

响应数据转换调用类似于请求数据转换调用, 对响应体进行一系列的处理(transformResponse 为处理函数的数组,defaults 中包含默认的配置)

response.data = transformData( 
  response.data, 
  response.headers, 
  config.transformResponse 
);
四、结语

上述三章对 Axios 进行整体的分析,从 Axios 的特点、整体设计及关键环节三个方面进行了讲述,通过阅读源码学到了很多知识,也能够更加熟练的使用 Axios。为了保证各位老铁的学习 Axios 源码的效果,对学习 Axios 源码的两条建议:

    边阅读本文边看源码,能够有更深入的理解。
    不要纠结于具体的实现,从宏观的角度去看源码,这样能够节省大量时间。

阿里云 2 核 2G 服务器 3M 带宽 61 元 1 年,有高配

腾讯云新客低至 82 元 / 年,老客户 99 元 / 年

代金券:在阿里云专用满减优惠券

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-07-25发表,共计5522字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中