三步法解析Axios源码

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 三步法解析Axios源码

一、领悟思想


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


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


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


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


二、把握设计



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

2.1 源码目录


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

640.png


2.2 抽象接口

640.png

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)
})

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

640.png

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

640.jpg


其核心原理就是这个吗?是的,你没有看错,这就是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. 请求拦截器就是在发送请求前执行的回调函数,个人认为其最大功用就是对多个请求的配置进行统一修改
  2. 仔细观察发现请求拦截器1先加入但是后执行,是不是与整体运行流程中的代码对上了。
  3. 响应拦截器就是在请求得到响应后执行的回调函数,成功回调的参数就是响应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,它一共做了三件事:

  1. 调用请求数据转换器转换请求数据


  1. 选择合适的适配器发起请求——自己配置了就选自己的,自己没有配置就选默认的(浏览器端就选xhrAdapter、node端就选httpAdapter;这也就是为什么Axios即支持浏览器又支持Node的原因)


  1. 当请求数据返回后,调用响应数据转换器转换响应数据


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源码的两条建议:

  1. 边阅读本文边看源码,能够有更深入的理解。


  1. 不要纠结于具体的实现,从宏观的角度去看源码,这样能够节省大量时间。









相关文章
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
76 2
|
2天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
2天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
2天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
25天前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
53 12
|
21天前
|
PyTorch Shell API
Ascend Extension for PyTorch的源码解析
本文介绍了Ascend对PyTorch代码的适配过程,包括源码下载、编译步骤及常见问题,详细解析了torch-npu编译后的文件结构和三种实现昇腾NPU算子调用的方式:通过torch的register方式、定义算子方式和API重定向映射方式。这对于开发者理解和使用Ascend平台上的PyTorch具有重要指导意义。
|
3天前
|
安全 搜索推荐 数据挖掘
陪玩系统源码开发流程解析,成品陪玩系统源码的优点
我们自主开发的多客陪玩系统源码,整合了市面上主流陪玩APP功能,支持二次开发。该系统适用于线上游戏陪玩、语音视频聊天、心理咨询等场景,提供用户注册管理、陪玩者资料库、预约匹配、实时通讯、支付结算、安全隐私保护、客户服务及数据分析等功能,打造综合性社交平台。随着互联网技术发展,陪玩系统正成为游戏爱好者的新宠,改变游戏体验并带来新的商业模式。
|
1月前
|
存储 安全 Linux
Golang的GMP调度模型与源码解析
【11月更文挑战第11天】GMP 调度模型是 Go 语言运行时系统的核心部分,用于高效管理和调度大量协程(goroutine)。它通过少量的操作系统线程(M)和逻辑处理器(P)来调度大量的轻量级协程(G),从而实现高性能的并发处理。GMP 模型通过本地队列和全局队列来减少锁竞争,提高调度效率。在 Go 源码中,`runtime.h` 文件定义了关键数据结构,`schedule()` 和 `findrunnable()` 函数实现了核心调度逻辑。通过深入研究 GMP 模型,可以更好地理解 Go 语言的并发机制。
|
1月前
|
消息中间件 缓存 安全
Future与FutureTask源码解析,接口阻塞问题及解决方案
【11月更文挑战第5天】在Java开发中,多线程编程是提高系统并发性能和资源利用率的重要手段。然而,多线程编程也带来了诸如线程安全、死锁、接口阻塞等一系列复杂问题。本文将深度剖析多线程优化技巧、Future与FutureTask的源码、接口阻塞问题及解决方案,并通过具体业务场景和Java代码示例进行实战演示。
58 3
|
2月前
|
前端开发 JavaScript UED
axios取消请求CancelToken的原理解析及用法示例
axios取消请求CancelToken的原理解析及用法示例
158 0

推荐镜像

更多