axios的原理及实现一个简易版axios

面试官:你了解axios的原理吗?有看过它的源码吗?

一、axios的使用

关于axios的基本使用,上篇文章已经有所涉及,这里再稍微回顾下:

发送请求

import axios from 'axios';axios(config) // 直接传入配置
axios(url[, config]) // 传入url和配置
axios[method](url[, option]) // 直接调用请求方式方法,传入url和配置
axios[method](url[, data[, option]]) // 直接调用请求方式方法,传入data、url和配置
axios.request(option) // 调用 request 方法const axiosInstance = axios.create(config)
// axiosInstance 也具有以上 axios 的能力axios.all([axiosInstance1, axiosInstance2]).then(axios.spread(response1, response2))
// 调用 all 和传入 spread 回调

请求拦截器

axios.interceptors.request.use(function (config) {// 这里写发送请求前处理的代码return config;
}, function (error) {// 这里写发送请求错误相关的代码return Promise.reject(error);
});

响应拦截器

axios.interceptors.response.use(function (response) {// 这里写得到响应数据后处理的代码return response;
}, function (error) {// 这里写得到错误响应处理的代码return Promise.reject(error);
});

取消请求

// 方式一
const CancelToken = axios.CancelToken;
const source = CancelToken.source();axios.get('xxxx', {cancelToken: source.token
})
// 取消请求 (请求原因是可选的)
source.cancel('主动取消请求');// 方式二
const CancelToken = axios.CancelToken;
let cancel;axios.get('xxxx', {cancelToken: new CancelToken(function executor(c) {cancel = c;})
});
cancel('主动取消请求');

二、实现一个简易版axios

构建一个Axios构造函数,核心代码为request

class Axios {constructor() {}request(config) {return new Promise(resolve => {const {url = '', method = 'get', data = {}} = config;// 发送ajax请求const xhr = new XMLHttpRequest();xhr.open(method, url, true);xhr.onload = function() {console.log(xhr.responseText)resolve(xhr.responseText);}xhr.send(data);})}
}

导出axios实例

// 最终导出axios的方法,即实例的request方法
function CreateAxiosFn() {let axios = new Axios();let req = axios.request.bind(axios);return req;
}// 得到最后的全局变量axios
let axios = CreateAxiosFn();

上述就已经能够实现axios({ })这种方式的请求

下面是来实现下axios.method()这种形式的请求

// 定义get,post...方法,挂在到Axios原型上
const methodsArr = ['get', 'delete', 'head', 'options', 'put', 'patch', 'post'];
methodsArr.forEach(met => {Axios.prototype[met] = function() {console.log('执行'+met+'方法');// 处理单个方法if (['get', 'delete', 'head', 'options'].includes(met)) { // 2个参数(url[, config])return this.request({method: met,url: arguments[0],...arguments[1] || {}})} else { // 3个参数(url[,data[,config]])return this.request({method: met,url: arguments[0],data: arguments[1] || {},...arguments[2] || {}})}}
})

Axios.prototype上的方法搬运到request

首先实现个工具类,实现将b方法混入到a,并且修改this指向

const utils = {extend(a,b, context) {for(let key in b) {if (b.hasOwnProperty(key)) {if (typeof b[key] === 'function') {a[key] = b[key].bind(context);} else {a[key] = b[key]}}}}
}

修改导出的方法

function CreateAxiosFn() {let axios = new Axios();let req = axios.request.bind(axios);// 增加代码utils.extend(req, Axios.prototype, axios)return req;
}

构建拦截器的构造函数

class InterceptorsManage {constructor() {this.handlers = [];}use(fullfield, rejected) {this.handlers.push({fullfield,rejected})}
}

实现axios.interceptors.response.useaxios.interceptors.request.use

class Axios {constructor() {// 新增代码this.interceptors = {request: new InterceptorsManage,response: new InterceptorsManage}}request(config) {...}
}

执行语句axios.interceptors.response.useaxios.interceptors.request.use的时候,实现获取axios实例上的interceptors对象,然后再获取responserequest拦截器,再执行对应的拦截器的use方法

Axios上的方法和属性搬到request过去

function CreateAxiosFn() {let axios = new Axios();let req = axios.request.bind(axios);// 混入方法, 处理axios的request方法,使之拥有get,post...方法utils.extend(req, Axios.prototype, axios)// 新增代码utils.extend(req, axios)return req;
}

现在request也有了interceptors对象,在发送请求的时候,会先获取request拦截器的handlers的方法来执行

首先将执行ajax的请求封装成一个方法

request(config) {this.sendAjax(config)
}
sendAjax(config){return new Promise(resolve => {const {url = '', method = 'get', data = {}} = config;// 发送ajax请求console.log(config);const xhr = new XMLHttpRequest();xhr.open(method, url, true);xhr.onload = function() {console.log(xhr.responseText)resolve(xhr.responseText);};xhr.send(data);})
}

获得handlers中的回调

request(config) {// 拦截器和请求组装队列let chain = [this.sendAjax.bind(this), undefined] // 成对出现的,失败回调暂时不处理// 请求拦截this.interceptors.request.handlers.forEach(interceptor => {chain.unshift(interceptor.fullfield, interceptor.rejected)})// 响应拦截this.interceptors.response.handlers.forEach(interceptor => {chain.push(interceptor.fullfield, interceptor.rejected)})// 执行队列,每次执行一对,并给promise赋最新的值let promise = Promise.resolve(config);while(chain.length > 0) {promise = promise.then(chain.shift(), chain.shift())}return promise;
}

chains大概是['fulfilled1','reject1','fulfilled2','reject2','this.sendAjax','undefined','fulfilled2','reject2','fulfilled1','reject1']这种形式

这样就能够成功实现一个简易版axios

三、源码分析

首先看看目录结构

axios发送请求有很多实现的方法,实现入口文件为axios.js

function createInstance(defaultConfig) {var context = new Axios(defaultConfig);// instance指向了request方法,且上下文指向context,所以可以直接以 instance(option) 方式调用 // Axios.prototype.request 内对第一个参数的数据类型判断,使我们能够以 instance(url, option) 方式调用var instance = bind(Axios.prototype.request, context);// 把Axios.prototype上的方法扩展到instance对象上,// 并指定上下文为context,这样执行Axios原型链上的方法时,this会指向contextutils.extend(instance, Axios.prototype, context);// Copy context to instance// 把context对象上的自身属性和方法扩展到instance上// 注:因为extend内部使用的forEach方法对对象做for in 遍历时,只遍历对象本身的属性,而不会遍历原型链上的属性// 这样,instance 就有了  defaults、interceptors 属性。utils.extend(instance, context);return instance;
}// Create the default instance to be exported 创建一个由默认配置生成的axios实例
var axios = createInstance(defaults);// Factory for creating new instances 扩展axios.create工厂函数,内部也是 createInstance
axios.create = function create(instanceConfig) {return createInstance(mergeConfig(axios.defaults, instanceConfig));
};// Expose all/spread
axios.all = function all(promises) {return Promise.all(promises);
};axios.spread = function spread(callback) {return function wrap(arr) {return callback.apply(null, arr);};
};
module.exports = axios;

主要核心是 Axios.prototype.request,各种请求方式的调用实现都是在 request 内部实现的, 简单看下 request 的逻辑

Axios.prototype.request = function request(config) {// Allow for axios('example/url'[, config]) a la fetch API// 判断 config 参数是否是 字符串,如果是则认为第一个参数是 URL,第二个参数是真正的configif (typeof config === 'string') {config = arguments[1] || {};// 把 url 放置到 config 对象中,便于之后的 mergeConfigconfig.url = arguments[0];} else {// 如果 config 参数是否是 字符串,则整体都当做configconfig = config || {};}// 合并默认配置和传入的配置config = mergeConfig(this.defaults, config);// 设置请求方法config.method = config.method ? config.method.toLowerCase() : 'get';/*something... 此部分会在后续拦截器单独讲述*/
};// 在 Axios 原型上挂载 'delete', 'get', 'head', 'options' 且不传参的请求方法,实现内部也是 request
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {Axios.prototype[method] = function(url, config) {return this.request(utils.merge(config || {}, {method: method,url: url}));};
});// 在 Axios 原型上挂载 'post', 'put', 'patch' 且传参的请求方法,实现内部同样也是 request
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {Axios.prototype[method] = function(url, data, config) {return this.request(utils.merge(config || {}, {method: method,url: url,data: data}));};
});

request入口参数为config,可以说config贯彻了axios的一生

axios 中的 config 主要分布在这几个地方:

  • 默认配置 defaults.js
  • config.method默认为 get
  • 调用 createInstance 方法创建 axios 实例,传入的config
  • 直接或间接调用 request 方法,传入的 config
// axios.js
// 创建一个由默认配置生成的axios实例
var axios = createInstance(defaults);// 扩展axios.create工厂函数,内部也是 createInstance
axios.create = function create(instanceConfig) {return createInstance(mergeConfig(axios.defaults, instanceConfig));
};// Axios.js
// 合并默认配置和传入的配置
config = mergeConfig(this.defaults, config);
// 设置请求方法
config.method = config.method ? config.method.toLowerCase() : 'get';

从源码中,可以看到优先级:默认配置对象default < method:get < Axios的实例属性this.default < request参数

下面重点看看request方法

Axios.prototype.request = function request(config) {/*先是 mergeConfig ... 等,不再阐述*/// Hook up interceptors middleware 创建拦截器链. dispatchRequest 是重中之重,后续重点var chain = [dispatchRequest, undefined];// push各个拦截器方法 注意:interceptor.fulfilled 或 interceptor.rejected 是可能为undefinedthis.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {// 请求拦截器逆序 注意此处的 forEach 是自定义的拦截器的forEach方法chain.unshift(interceptor.fulfilled, interceptor.rejected);});this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {// 响应拦截器顺序 注意此处的 forEach 是自定义的拦截器的forEach方法chain.push(interceptor.fulfilled, interceptor.rejected);});// 初始化一个promise对象,状态为resolved,接收到的参数为已经处理合并过的config对象var promise = Promise.resolve(config);// 循环拦截器的链while (chain.length) {promise = promise.then(chain.shift(), chain.shift()); // 每一次向外弹出拦截器}// 返回 promisereturn promise;
};

拦截器interceptors是在构建axios实例化的属性

function Axios(instanceConfig) {this.defaults = instanceConfig;this.interceptors = {request: new InterceptorManager(), // 请求拦截response: new InterceptorManager() // 响应拦截};
}

InterceptorManager构造函数

// 拦截器的初始化 其实就是一组钩子函数
function InterceptorManager() {this.handlers = [];
}// 调用拦截器实例的use时就是往钩子函数中push方法
InterceptorManager.prototype.use = function use(fulfilled, rejected) {this.handlers.push({fulfilled: fulfilled,rejected: rejected});return this.handlers.length - 1;
};// 拦截器是可以取消的,根据use的时候返回的ID,把某一个拦截器方法置为null
// 不能用 splice 或者 slice 的原因是 删除之后 id 就会变化,导致之后的顺序或者是操作不可控
InterceptorManager.prototype.eject = function eject(id) {if (this.handlers[id]) {this.handlers[id] = null;}
};// 这就是在 Axios的request方法中 中循环拦截器的方法 forEach 循环执行钩子函数
InterceptorManager.prototype.forEach = function forEach(fn) {utils.forEach(this.handlers, function forEachHandler(h) {if (h !== null) {fn(h);}});
}

请求拦截器方法是被 unshift到拦截器中,响应拦截器是被push到拦截器中的。最终它们会拼接上一个叫dispatchRequest的方法被后续的 promise 顺序执行

var utils = require('./../utils');
var transformData = require('./transformData');
var isCancel = require('../cancel/isCancel');
var defaults = require('../defaults');
var isAbsoluteURL = require('./../helpers/isAbsoluteURL');
var combineURLs = require('./../helpers/combineURLs');// 判断请求是否已被取消,如果已经被取消,抛出已取消
function throwIfCancellationRequested(config) {if (config.cancelToken) {config.cancelToken.throwIfRequested();}
}module.exports = function dispatchRequest(config) {throwIfCancellationRequested(config);// 如果包含baseUrl, 并且不是config.url绝对路径,组合baseUrl以及config.urlif (config.baseURL && !isAbsoluteURL(config.url)) {// 组合baseURL与url形成完整的请求路径config.url = combineURLs(config.baseURL, config.url);}config.headers = config.headers || {};// 使用/lib/defaults.js中的transformRequest方法,对config.headers和config.data进行格式化// 比如将headers中的Accept,Content-Type统一处理成大写// 比如如果请求正文是一个Object会格式化为JSON字符串,并添加application/json;charset=utf-8的Content-Type// 等一系列操作config.data = transformData(config.data,config.headers,config.transformRequest);// 合并不同配置的headers,config.headers的配置优先级更高config.headers = utils.merge(config.headers.common || {},config.headers[config.method] || {},config.headers || {});// 删除headers中的method属性utils.forEach(['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],function cleanHeaderConfig(method) {delete config.headers[method];});// 如果config配置了adapter,使用config中配置adapter的替代默认的请求方法var adapter = config.adapter || defaults.adapter;// 使用adapter方法发起请求(adapter根据浏览器环境或者Node环境会有不同)return adapter(config).then(// 请求正确返回的回调function onAdapterResolution(response) {// 判断是否以及取消了请求,如果取消了请求抛出以取消throwIfCancellationRequested(config);// 使用/lib/defaults.js中的transformResponse方法,对服务器返回的数据进行格式化// 例如,使用JSON.parse对响应正文进行解析response.data = transformData(response.data,response.headers,config.transformResponse);return response;},// 请求失败的回调function onAdapterRejection(reason) {if (!isCancel(reason)) {throwIfCancellationRequested(config);if (reason && reason.response) {reason.response.data = transformData(reason.response.data,reason.response.headers,config.transformResponse);}}return Promise.reject(reason);});
};

再来看看axios是如何实现取消请求的,实现文件在CancelToken.js

function CancelToken(executor) {if (typeof executor !== 'function') {throw new TypeError('executor must be a function.');}// 在 CancelToken 上定义一个 pending 状态的 promise ,将 resolve 回调赋值给外部变量 resolvePromisevar resolvePromise;this.promise = new Promise(function promiseExecutor(resolve) {resolvePromise = resolve;});var token = this;// 立即执行 传入的 executor函数,将真实的 cancel 方法通过参数传递出去。// 一旦调用就执行 resolvePromise 即前面的 promise 的 resolve,就更改promise的状态为 resolve。// 那么xhr中定义的 CancelToken.promise.then方法就会执行, 从而xhr内部会取消请求executor(function cancel(message) {// 判断请求是否已经取消过,避免多次执行if (token.reason) {return;}token.reason = new Cancel(message);resolvePromise(token.reason);});
}CancelToken.source = function source() {// source 方法就是返回了一个 CancelToken 实例,与直接使用 new CancelToken 是一样的操作var cancel;var token = new CancelToken(function executor(c) {cancel = c;});// 返回创建的 CancelToken 实例以及取消方法return {token: token,cancel: cancel};
};

实际上取消请求的操作是在 xhr.js 中也有响应的配合的

if (config.cancelToken) {config.cancelToken.promise.then(function onCanceled(cancel) {if (!request) {return;}// 取消请求request.abort();reject(cancel);});
}

巧妙的地方在 CancelTokenexecutor 函数,通过resolve函数的传递与执行,控制promise的状态

小结

参考文献

  • https://juejin.cn/post/6856706569263677447#heading-4
  • https://juejin.cn/post/6844903907500490766
  • https://github.com/axios/axios

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/152823.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第十五章---I/O(输入/输出)

15.1输入输出流 流是一组有序的数据序列&#xff0c;根据操作的类型&#xff0c;可分为输入流和输出流两种。I/O(Input/Output,(输出)流提供了一条通道程序&#xff0c;可以使用这条通道把源中的字节序列送到目的地。虽然 I/O 流疆盘文件存取有关&#xff0c;但是程序的源和目…

华为---OSPF网络虚连接(Virtual Link)简介及示例配置

OSPF网络虚连接&#xff08;Virtual Link&#xff09;简介 为了避免区域间的环路&#xff0c;OSPF规定不允许直接在两个非骨干区域之间发布路由信息&#xff0c;只允许在一个区域内部或者在骨干区域和非骨干区域之间发布路由信息。因此&#xff0c;每个ABR都必须连接到骨干区域…

QT基础学习

2创建项目 2.1使用向导创建 打开Qt Creator 界面选择 New Project或者选择菜单栏 【文件】-【新建文件或项目】菜单项 弹出New Project对话框&#xff0c;选择Qt Widgets Application&#xff0c; 选择【Choose】按钮&#xff0c;弹出如下对话框 设置项目名称和路径&#xf…

N 字形变换

将一个给定字符串 s 根据给定的行数 numRows &#xff0c;以从上往下、从左到右进行 Z 字形排列。 比如输入字符串为 “PAYPALISHIRING” 行数为 3 时&#xff0c;排列如下&#xff1a; P A H N A P L S I I G Y I R 之后&#xff0c;你的输出需要从左往右逐行读取&#xff0…

网络参考模型与标准协议(一)

OSI参考模型 OSI 模型(Open Systems Interconnection Model)&#xff0c;由国际化标准组织ISO (TheInternational Organization for Standardization )收录在ISO 7489标准中并于1984年发布。 OSI参考模型又被称为七层模型&#xff0c;由下至上依次为: 物理层: 在设备之间传输比…

Linux编辑器-gcc/g++使用

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大二&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;熟练使用gcc/g编译器 > 毒鸡汤&#xff1a;真正…

75基于matlab的模拟退火算法优化TSP(SA-TSP),最优路径动态寻优,输出最优路径值、路径曲线、迭代曲线。

基于matlab的模拟退火算法优化TSP(SA-TSP)&#xff0c;最优路径动态寻优&#xff0c;输出最优路径值、路径曲线、迭代曲线。数据可更换自己的&#xff0c;程序已调通&#xff0c;可直接运行。 75matlab模拟退火算法TSP问题 (xiaohongshu.com)

如何通过cpolar内网穿透工具实现远程访问本地postgreSQL

文章目录 前言1. 安装postgreSQL2. 本地连接postgreSQL3. Windows 安装 cpolar4. 配置postgreSQL公网地址5. 公网postgreSQL访问6. 固定连接公网地址7. postgreSQL固定地址连接测试 前言 PostgreSQL是一个功能非常强大的关系型数据库管理系统&#xff08;RDBMS&#xff09;,下…

uniapp开发小程序,包过大解决方案

1、首先和大家说一下 微信小程序 主包限制不能超过2M 分包一共不能超过8M 然后具体解决优化步骤如下&#xff0c; 将主包进行分包 在pages.json 下subPackages里面进行配置分包 分包配置完 配置过的文件都需要进行修改对应的路径 2 、 在运行的时候 一定要勾选 压缩代码 有…

Android Termux安装MySQL,内网穿透实现公网远程访问

文章目录 前言1.安装MariaDB2.安装cpolar内网穿透工具3. 创建安全隧道映射mysql4. 公网远程连接5. 固定远程连接地址 前言 Android作为移动设备&#xff0c;尽管最初并非设计为服务器&#xff0c;但是随着技术的进步我们可以将Android配置为生产力工具&#xff0c;变成一个随身…

【电路笔记】-欧姆定律

欧姆定律 文章目录 欧姆定律1、概述2、AC电路的等效性2.1 输入电阻2.2 输入电感2.3 输入电容 3、欧姆定律的局部形式3.1 介绍和定义3.2 德鲁德模型(Drude Model)3.3 局部形式表达式 4、电阻和宏观欧姆定律5、总结 电流、电压和电阻之间的基本关系被称为欧姆定律&#xff0c;可能…

cpu飙高问题,案例分析(一)

一、复习知识点&#xff1a; CPU性能指标&#xff1a; load average&#xff1a;负载&#xff0c;linux查看的时候&#xff0c;通常显示如下&#xff1a; load average后面有三段数字&#xff1a;代表了系统1分钟&#xff0c;5分钟&#xff0c;15分钟平均负载。 形象的类别可…

使用npm发布自己的组件库

在日常开发中&#xff0c;我们习惯性的会封装一些个性化的组件以适配各种业务场景&#xff0c;突发奇想能不能建一个自己的组件库&#xff0c;今后在各种业务里可以自由下载安装自己的组件。 一. 项目搭建 首先直接使用vue-cli创建一个vue2版本的项目&#xff0c;并下载好ele…

Ps:陷印

在准备图像进行专业印刷之前&#xff0c;陷印 Trap是一个重要的步骤。 在彩色印刷中&#xff0c;多种颜色的墨水通常分别印刷。陷印是一种叠印技术&#xff0c;它可避免打印时印版的微小偏差或移动而使打印图像出现微小的缝隙。 进行陷印处理以纠正未对齐现象 A. 未对齐现象&am…

代码逻辑修复与其他爬虫ip库的应用

在一个项目中&#xff0c;由于需要设置 http_proxy 来爬虫IP访问网络&#xff0c;但在使用 requests 库下载文件时遇到了问题。具体表现为在执行 Python 脚本时&#xff0c;程序会阻塞并最终超时&#xff0c;无法正常完成文件下载。 解决方案 针对这个问题&#xff0c;我们可以…

VB.net读写S50/F08IC卡,修改卡片密码控制位源码

本示例使用设备&#xff1a;Android Linux RFID读写器NFC发卡器WEB可编程NDEF文本/智能海报/-淘宝网 (taobao.com) 函数声明 Module Module1读卡函数声明Public Declare Function piccreadex Lib "OUR_MIFARE.dll" (ByVal ctrlword As Byte, ByRef serial As Byte, …

基于单片机K型热电偶温度采集报警系统

**单片机设计介绍&#xff0c; 基于单片机K型热电偶温度采集报警系统 文章目录 一 概要简介系统特点系统组成工作原理应用领域 二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 # 基于单片机K型热电偶温度采集报警系统介绍 简介 该系统是基于单片…

【论文阅读笔记】Deep learning for time series classification: a review

【论文阅读笔记】Deep learning for time series classification: a review 摘要 在这篇文章中&#xff0c;作者通过对TSC的最新DNN架构进行实证研究&#xff0c;探讨了深度学习算法在TSC中的当前最新性能。文章提供了对DNNs在TSC的统一分类体系下在各种时间序列领域中的最成功…

8 Redis与Lua

LUA脚本语言是C开发的&#xff0c;类似存储过程,是为了实现完整的原子性操作&#xff0c;可以用来补充redis弱事务的缺点. 1、LUA脚本的好处 2、Lua脚本限流实战 支持分布式 import org.springframework.core.io.ClassPathResource; import org.springframework.data.redis…

Excel 文件比较工具 xlCompare 11.01 Crack

比较两个 Excel 文件之间的差异 xlCompare. xlCompare.com 是性能最佳的 Excel diff 工具&#xff0c;用于比较两个 Excel 文件或工作表并在线突出显示差异。xlCompare 包括免费的在线 Excel 和 CSV 文件比较服务以及用于比较和合并 Excel 文件的强大桌面工具。如果您想在线了…