模拟实现axios对象创建过程
构造函数Axios,在Axios原型上添加相关的方法,声明一个createInstance()函数,在函数内部实例化一个对象context和创建一个请求对象函数instance,遍历将Axios.prototype对象中的方法添加到instance函数对象中,然后遍历将defaults与intercepters属性添加到instance上,然后返回instance。最后通过new createInstance()来实例化构造axios。
<script>
// 构造函数
function Axios(config){
// 初始化
this.defaults = config;//为了创建default默认属性
this.intercepters = {
request:{},
Response:{}
}
}
// 原型添加相关的方法
Axios.prototype.request = function(config){
console.log('发送AJAX请求 请求类型为' + config.method);
}
//能发送请求的原理是因为Axios内部调用了request方法
Axios.prototype.get = function(config){
return this.request({method: 'GET'});
}
Axios.prototype.post = function(config){
return this.request({method: 'POST'});
}
// 声明函数
function createInstance(config){
// 实例化一个对象
let context = new Axios(config);//context.get() context.post() 但是不能当做函数使用context()
// 创建请求对象函数
let instance = Axios.prototype.request.bind(context);//instance是一个函数 并且可以instance({}) 此时instance不能instance.get
// 将Axios.prototype对象中的方法添加到instance函数对象中
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: 'GET'});
axios.get({});
axios.post({});
</script>
模拟实现axios发送请求过程
<script>
// axios发送请求 axios Axios.prototype.request bind
// 1.声明一个构造函数
function Axios(config){
this.config = config;
}
Axios.prototype.request = function(config){
// 发送请求
// 创建一个promise对象
let promise = Promise.resolve(config);
// 声明一个数组
let chains = [dispatchRequest, undefined];//undefined 占位
// 调用then方法指定回调
let result = promise.then(chains[0], chains[1]);
// 返回promise的结果
return result;
}
// 2.dispatchRequest函数
function dispatchRequest(config){
// 调用适配器发送请求
return xhrAdapter(config).then(response => {
//响应的结果进行转换处理
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(),//字符串
//xhr 请求对象
request:xhr,
// 响应状态码
status:xhr.status,
// 响应状态字符串
statusText:xhr.statusText
});
}else{
// 失败的状态
reject(new Error('请求失败 失败的状态码为' + xhr.status));
}
}
}
})
}
// 4.创建axios函数
let axios = Axios.prototype.request.bind(null);
axios({
method:'GET',
url:'http://localhost:3000/posts'
}).then(response => {
console.log(response);
});
</script>
模拟实现axios拦截器功能
请求拦截器:
在真正发送请求前执行的回调函数
可以对请求进行检查、配置或者特定处理
成功的回调函数:传递默认的是config(必须)
失败的回调函数:传递的默认是error
响应拦截器:
在请求得到响应后执行的回调函数
可以对响应数据进行特定处理
成功的回调函数:默认传递的是response
失败的回调函数:默认传递的是error
<script>
// 构造函数
function Axios(config){
this.config = config;
this.interceptors = {
request: new interceptorManager(),
response: new interceptorManager()
}
}
// 发送请求
Axios.prototype.request = function(config){
//创建一个promise对象
let promise = Promise.resolve(config);
// 创建一个数组
const chains = [dispatchRequest, undefined];
//处理拦截器
//请求拦截器 将请求拦截器的回调 压入到chains前面 request.handles = []
this.interceptors.request.handlers.forEach(item => {
chains.unshift(item.fulfilled, item.rejected);
});
//响应拦截器
this.interceptors.response.handlers.forEach(item => {
chains.push(item.fulfilled, item.rejected);
});
//遍历
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'
});
})
}
//创建实例
let context = new Axios({});
// 创建axios函数
let axios = Axios.prototype.request.bind(context);
//将context属性 config interceptors添加至axios函数对象身上
Object.keys(context).forEach(key => {
axios[key] = context[key];
})
// 拦截器管理器 构造函数
function interceptorManager(){
this.handlers = [];
}
interceptorManager.prototype.use = function(fulfilled, rejected){
this.handlers.push({
fulfilled,
rejected
})
}
// 以下为功能测试代码
// 设置请求拦截器 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);
});
// 发送请求
axios({
method: 'GET',
url: 'http://localhost:3000/posts'
}).then(response => {
console.log(response);
})
</script>
模拟实现axios取消请求功能
在CancelToken身上维护了一个属性promise,然后将改变promise状态的变量resolvePromise暴露到全局中,cancel便成为了“钥匙”,只要cancel()一调用,内部的resolvePromise便执行, this.promise状态便变为成功,然后发送请求中的回调便执行,进而xhr.abort()。
<body>
<div class="container">
<h2 class="page-header">axios取消请求</h2>
<button class="btn btn-primary">发送请求</button>
<button class="btn btn-warning">取消请求</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);
}
// xhrAdapt 函数
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;
//为实例对象添加属性
this.promise = new Promise((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的值
var cancelToken = new CancelToken(function(c){
cancel = c;
});
axios({
method: 'GET',
url: 'http://localhost:3000/posts',
// 1.添加配置对象的属性
cancelToken: cancelToken
}).then(response => {
console.log(response);
// 将cancel的值初始化
cancel = null;
})
}
//取消请求
btns[1].onclick = function(){
cancel();
}
</script>
axios与Axios的关系
-
从语法上来说:axios不是Axios的实例
axios是通过createInstance()创建来的,先造一个实例对象context,再造一个函数对象instance,把实例对象context原型上的方法放到instance函数身上,再把实例对象的属性加到Instance函数对象身上,最终返回instance,然后实例化createInstance形成axios。
function createInstance(defaultConfig) {
//创建一个实例对象 context 可以调用 get post put delete request
var context = new Axios(defaultConfig);// context 不能当函数使用
// 将 request 方法的 this 指向 context 并返回新函数 instance 可以用作函数使用, 且返回的是一个 promise 对象
var instance = bind(Axios.prototype.request, context);// instance 与 Axios.prototype.request 代码一致
// instance({method:'get'}); instance.get() .post()
// Copy axios.prototype to instance
// 将 Axios.prototype 和实例对象的方法都添加到 instance 函数身上
utils.extend(instance, Axios.prototype, context);// instance.get instance.post ...
// instance() instance.get()
// 将实例对象的方法和属性扩展到 instance 函数身上
utils.extend(instance, context);
return instance;
}
// axios.interceptors
// Create the default instance to be exported
// 通过配置创建 axios 函数
var axios = createInstance(defaults);
-
从功能上来说:axios是Axios的实例
axios拥有Axios实例对象上的方法,get、post等(通过扩展的方式把原型上的方法加到函数对象instance身上)。
-
axios是Axios.prototype.request函数bind()返回的函数
-
axios作为对象有Axios原型对象上的所有方法,有Axios对象上的所有属性
instance与axios的区别
相同点:
(1)都是一个能够法任意请求的函数:request(config)
(2)都有特定请求的各种方法:get()/post()/put()/delete()
(3)都有默认配置和拦截器的属性:defaults/interceptors
不同点:
(1)默认配置很可能不一样
(2)instance没有axios后面添加的一些方法:create()/CancelToken()/all()
// 通过配置创建 axios 函数
var axios = createInstance(defaults);
// Expose Axios class to allow class inheritance
// axios 添加 Axios 属性, 属性值为构造函数对象 axios.CancelToken = CancelToken new axios.Axios();
axios.Axios = Axios;
// Factory for creating new instances
// 工厂函数 用来返回创建实例对象的函数
axios.create = function create(instanceConfig) {
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
// Expose Cancel & CancelToken
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;
//简单实现全局暴露 axios
window.axios = axios;
// Allow use of default import syntax in TypeScript
module.exports.default = axios;