/dist/ 输出目录,最后输出的axios整体文件
axios.js 未压缩的axios.min.js 压缩后的/lib/ 源码目录,所有的源代码都放在这里
/adapters/ 请求的适配器
http.js 实现http适配器(包装 http包),在 node.js向远程服务器发送请求时使用xhr.js 实现 xhr适配器(包装 xhr对象),在浏览器端发送请求时使用/cancel/ 定义取消功能
Cancel.js 构造函数,用来创建取消时的错误对象CancelToken.js取消请求的构造函数isCancel.js 检测参数是否为取消对象/core/ 一些核心功能
Axios.js axios的核心主类,文件存放的是 axios的构造函数buildFullPath.js 构建完整 URL 的函数文件createError.js 创建指定信息的 Error 对象dispatchRequest.js 发送请求的函数文件enhanceError.js 更新错误对象的函数文件InterceptorManager.js 的管理器mergeConfig.js 合并配置的函数文件settle.js 根据 http响应状态,改变Promise 的状态transformData.js 数据格式转化函数/helpers/ 一些功能函数
bind.js 返回一个新的函数,并将新函数this绑定到一个对象身上buildURL.js 创建一个 URL,将参数缀到URL 后,并返回格式化后的内容combineURLs.js 合并URLcookies.js 处理cookiedeprecatedMethod.js 控制台提示不赞成使用的方法isAbsoluteURL.js 检测是否为绝对路径的 URLisURLSameOrigin.js 检测是否为同源的 URLnormalizeHeaderName.js 统一化头信息, 统一变为大写parseHeaders.js 解析将头信息解析为对象spread.js 用于调用函数和扩展参数数组的语法糖axios.js axios入口文件defaults.js axios配置文件utils.js 工具函数文件package.json 包信息index.d.ts 配置 TypeScript的声明文件index.js 整个包的入口文件模拟实现 axios 对象创建过程可以看下面代码以及代码注释。
<script>
// 构造函数
function Axios(config){
// 初始化
this.defaults = config;// 为了创建 default 默认属性
this.interceptors = {
request: {},
response: {}
}
}
// 原型添加相关的方法
Axios.prototype.request = function(config){
console.log('发送请求,请求的类型为 '+ config.method);
}
Axios.prototype.get = function(config){
return this.request({method: 'GET'});
}
Axios.prototype.post = function(config){
return this.request({method: 'POST'});
}
// 声明函数
function createInstance(config){
// 实例化一个对象
// 可以 context.get() context.post() 这样使用,但是不能当做函数使用,即不能 context() 这样使用
let context = new Axios(config);
// 创建请求函数
// 此时 instance 是一个函数,并且可以调用instance()然后往里面传对象,比如instance({})。
// 此时 instance 不能 instance.get 这样使用。
let instance = Axios.prototype.request.bind(context);
// 为了可以 instance.get 这样用,将 Axios.prototype 对象中的方法添加到 instance 函数对象中
// 为了保证函数在调用时 this 一定指向实例对象 context,更加严谨些,加个bind()
Object.keys(Axios.prototype).forEach(key => {
instance[key] = Axios.prototype[key].bind(context);
});
// 为 instance 这个函数对象添加 default 和 interceptors 属性
Object.keys(context).forEach(key => {
instance[key] = context[key];
});
return instance;
}
let axios = createInstance();
// 发送请求
// axios({method:'POST'});
axios.get({});
axios.post({});
</script>
axios是由Axios.prototype.request这个函数通过bind创建而来的,所以无论是 axios()还是axios.get()、axios.post()等方式,请求的源头都是request。
模拟实现 axios 发送请求的过程可以看下面代码以及代码注释。
<script>
// axios 发送请求
//1. 声明构造函数
function Axios(config){
this.config = config;
}
Axios.prototype.request = function(config){
//发送请求
//源码中在创建前做了合并处理等
//创建一个 promise 对象,可以看到这个 promise 一定是成功的
let promise = Promise.resolve(config);
//声明一个数组
let chains = [dispatchRequest, undefined];// 这里的 undefined 是一个占位
//调用 then 方法指定回调
//这里的 then 方法执行后会执行下面的 dispatchRequest 函数,函数的返回结果是由适配器 xhrAdapter 执行结果决定的。
//成功时候,这里 result 结果值就是下面的 response
let result = promise.then(chains[0], chains[1]);
//返回 promise 的结果
//这里的 result 就是 request 函数的执行结果,也就是 axios() 的执行结果
return result;
}
//2. dispatchRequest 函数
function dispatchRequest(config){
// 可以在这里进行对请求数据进行初始化转化、合并一切其他头信息的配置项等处理(源码中进行了处理)
//调用适配器发送请求
return xhrAdapter(config).then(response => {
//可以在这里对响应的结果进行转换处理(源码中进行了处理)
//这里的 response 就是 上面的 result 成功时候的结果值。
return response;
}, error => {
throw error;
});
}
//3. adapter 适配器
function xhrAdapter(config){
console.log('xhrAdapter 函数执行');
return new Promise((resolve, reject) => {
//发送 AJAX 请求
let xhr = new XMLHttpRequest();
//初始化
xhr.open(config.method, config.url);
//发送
xhr.send();
//绑定事件
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
//判断成功的条件
if(xhr.status >= 200 && xhr.status < 300){
//成功的状态
resolve({
//配置对象
config: config,
//响应体
data: xhr.response,
//响应头
headers: xhr.getAllResponseHeaders(), //是一个字符串,源码中是做了格式化,用 parseHeaders 将头信息解析成对象
// xhr 请求对象
request: xhr,
//响应状态码
status: xhr.status,
//响应状态字符串
statusText: xhr.statusText
});
}else{
//失败的状态
reject(new Error('请求失败,失败的状态码为' + xhr.status));
}
}
}
});
}
//4. 创建 axios 函数
let axios = Axios.prototype.request.bind(null);
//这里就是最上面 request 函数的执行结果 result
//然后执行.then 操作
axios({
method:'GET',
url:'http://localhost:3000/posts'
}).then(response => {
console.log(response);
});
</script>
在我之前的博客中提到过一个顺序的示例,当时执行结果是先执行2号请求,后执行1号请求,当时有解释原因。
原因如下:
promise在遍历执行时,使用的是数组的shift方法每次从中取出两个函数(成功回调,失败回调)执行。
而在遍历执行前,我们要先将的请求回调和响应回调都压入一个数组中,之后再进行遍历运行。
在向数组中添加 请求函数 时,根据axios请求的执行顺序,请求 应该在发送请求之前执行,所以应该添加在 发送请求函数 的前面,因此使用的是数组的unshift方法,即头部添加,故后面添加的 请求 总是放在头部。
故 请求2 先 请求1 后。
那么源码中 响应是使用的什么方法呢?它使用的是数组的push方法。
在向数组中添加 请求函数 时,根据axios请求的执行顺序,响应 应该在发送请求之后执行,所以应该添加在 发送请求函数 的后面,因此使用的是数组的push方法,即尾部添加,故后面添加的 响应 总是放在尾部。
故 响应1 先 响应2 后。
现在来模拟实现 axios 功能,具体可以看下面代码以及代码注释。
其中创建axios对象和发送请求可以看上面的代码,这里有些简略,只是搭了个框架。
<script>
//1.构造函数
function Axios(config){
this.config = config;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
}
}
//3.发送请求 难点与重点
Axios.prototype.request = function(config){
//源码中在创建前做了合并处理等
//创建一个 promise 对象
let promise = Promise.resolve(config);
//创建一个数组
const chains = [dispatchRequest, undefined];// 这里的 undefined 是一个占位
//处理
//请求
//将请求的回调 压入到 chains 的前面
this.interceptors.request.handlers.forEach(item => {
chains.unshift(item.fulfilled, item.rejected);
});
//响应
this.interceptors.response.handlers.forEach(item => {
chains.push(item.fulfilled, item.rejected);
});
// console.log(chains);
//promise 遍历执行,使用数组的 shift 方法每次从中取出两个函数(成功回调,失败回调)执行
while(chains.length > 0){
promise = promise.then(chains.shift(), chains.shift());
}
return promise;
}
//发送请求
function dispatchRequest(config){
//返回一个promise
return new Promise((resolve, reject) => {
resolve({
status: 200,
statusText: 'OK'
});
});
}
//2.管理器构造函数
function InterceptorManager(){
this.handlers = [];
}
//一旦调用use,就把成功和失败的回调函数做成一个对象压入到 handlers 中
InterceptorManager.prototype.use = function(fulfilled, rejected){
this.handlers.push({
fulfilled,
rejected
})
}
//4.创建实例
let context = new Axios({});
//创建axios函数
let axios = Axios.prototype.request.bind(context);
//为 axios 这个函数对象添加 context 的 default 与 interceptors 属性
Object.keys(context).forEach(key => {
axios[key] = context[key];
});
//5.以下为功能测试代码
// 设置请求 config 配置对象
axios.interceptors.request.use(function one(config) {
console.log('请求 成功 - 1号');
return config;
}, function one(error) {
console.log('请求 失败 - 1号');
return Promise.reject(error);
});
axios.interceptors.request.use(function two(config) {
console.log('请求 成功 - 2号');
return config;
}, function two(error) {
console.log('请求 失败 - 2号');
return Promise.reject(error);
});
// 设置响应
axios.interceptors.response.use(function (response) {
console.log('响应 成功 1号');
return response;
}, function (error) {
console.log('响应 失败 1号')
return Promise.reject(error);
});
axios.interceptors.response.use(function (response) {
console.log('响应 成功 2号')
return response;
}, function (error) {
console.log('响应 失败 2号')
return Promise.reject(error);
});
//6.发送请求
axios({
method: 'GET',
url: 'http://localhost:3000/posts'
}).then(response => {
console.log(response);
});
</script>
现在来模拟实现 axios 取消请求功能,具体可以看下面代码以及代码注释。
其中创建axios对象和发送请求可以看上面的代码,这里有些简略,只是搭了个框架。
<body>
<div class="container">
<h2 class="page-header">axios取消请求</h2>
<button class="btn btn-primary"> 发送请求 </button>
</div>
<script>
//构造函数
function Axios(config){
this.config = config;
}
//原型 request 方法
Axios.prototype.request = function(config){
return dispatchRequest(config);
}
//dispatchRequest 函数
function dispatchRequest(config){
return xhrAdapter(config);
}
//xhrAdapter
function xhrAdapter(config){
//发送 AJAX 请求
return new Promise((resolve, reject) => {
//实例化对象
const xhr = new XMLHttpRequest();
//初始化
xhr.open(config.method, config.url);
//发送
xhr.send();
//处理结果
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
//判断结果
if(xhr.status >= 200 && xhr.status < 300){
//设置为成功的状态
resolve({
status: xhr.status,
statusText: xhr.statusText
});
}else{
reject(new Error('请求失败'));
}
}
}
//关于取消请求的处理
if(config.cancelToken){
//对 cancelToken 对象身上的 promise 对象指定成功的回调
config.cancelToken.promise.then(value => {
// 取消请求
xhr.abort();
//将整体结果设置为失败
reject(new Error('请求已经被取消'))
});
}
})
}
// 创建 axios 函数
const context = new Axios({});
const axios = Axios.prototype.request.bind(context);
// CancelToken 构造函数
function CancelToken(executor){
// 声明一个变量
var resolvePromise;
// 为实例对象添加属性,这个 promise 属性的值是一个 Promise 对象,并且初始是 pedding 状态
this.promise = new Promise((resolve) => {
// 将 resolve 赋值给 resolvePromise
// 这样执行 resolvePromise 函数时,就能更改 promise 原状态
//这是因为,resolve 和 resolvePromise 都是引用类型,指向的是同一个内存地址,resolvePromise执行,则 resolve 也执行
resolvePromise = resolve
});
// 调用 executor 函数
executor(function(){
// 执行 resolvePromise 函数
resolvePromise();
});
}
// 获取按钮 以上为模拟实现的代码
const btns = document.querySelectorAll('button');
//声明全局变量
let cancel = null;
//发送请求
btns[0].onclick = function(){
//检测上一次的请求是否已经完成
if(cancel !== null){
//取消上一次的请求
cancel();
}
//创建 cancelToken 的值
let cancelToken = new CancelToken(function(c){
// 此时的 function(c){} 就是上面的CancelToken构造函数里的 executor,
// 所以此时的 c 就是上面的CancelToken构造函数里的 function(){resolvePromise()}
// 所以这里的 cancel 其实就是 function(){resolvePromise()}
// cancel 一旦执行,resolvePromise() 就会执行,CancelToken构造函数里的 promise 属性的状态就会改变
cancel = c;
});
axios({
method: 'GET',
url: 'http://localhost:3000/posts',
//添加配置对象的属性
cancelToken: cancelToken
}).then(response => {
console.log(response);
//将 cancel 的值初始化
cancel = null;
})
}
</script>
</body>
整体流程:request(config)=> dispatchRequest(config) => xhrAdapter(config)
request(config):将请求 / dispatchRequest()/ 响应 通过 promise链串连起来,返回 promisexhrAdapter()发请求 => 请求返回后转换响应数据,返回promise因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- 99spj.com 版权所有 湘ICP备2022005869号-5
违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务