导语
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
- 支持 Promise API
- 拦截请求和响应
- 转换请求数据和响应数据
说白了,axios就是使用Promise封装的XHR。
想要复刻我们首先要知道使用方法,如果你还不会使用axios,具体使用方法可以看我之前发的Axios
axios和axios.method
首先实现axios是一个方法,参数形式有三种,url、url+option、option,这里需要通过arguments来获取参数,然后判断参数的长度和类型进行不同的处理,这里就写一个大概的逻辑
然后,axios的返回值是一个Promise对象,所以函数的返回结果需要使用new Promise
包装一下
Axios.prototype.request = function () {
// 处理参数(粗略)
const args = Array.from(arguments);
let config = {}
if (args.length === 0) {
throw new Error('[axios]参数不能为空')
} else if (args.length === 1) {
if (typeof args[0] === 'string') {
config.url = args[0];
config.method = 'get'
} else if (Object.prototype.toString.call(args[0]) === '[object Object]') {
config = args[0]
} else {
throw new Error('[axios]参数格式错误')
}
} else {
if (typeof args[0] === 'string' && Object.prototype.toString.call(args[1]) === '[object Object]') {
config = args[1]
config.url = args[0]
}
}
return new Promise((resolve, reject) => {
const { url = '', method = 'get', data = {} } = config;
// 发送ajax请求
const xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.onload = function () {
if (xhr.status == 200)
resolve(xhr.responseText);
else
reject(xhr.responseText);
}
xhr.send(data);
})
}
axios的核心方法request已经完成了,接下来进行包装
处理,在使用时axios是一个方法,而不是一个实例,也就是说我们需要将request方法导出
function createAxiosInstance() {
const axios = new Axios();
let request = axios.request.bind(axios);
return request;
}
const axios = createAxiosInstance();
至于为什么要用这么绕的方法来处理,到最后你就懂了。
现在我们先来测试一下我们刚才的杰作,首先起一个server,为了方便就用express吧
const express = require('express')
const app = express()
app.all('*', function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Methods', '*');
res.header('Content-Type', 'application/json;charset=utf-8');
next();
});
app.get('/data', function (request, response) {
data = {
name: 'king',
age: 36,
sex: 'man',
hobby: 'code'
};
response.json(data);
});
app.listen(5005, () => {
console.log('server start and listen port 5005');
})
然后编写html页面,引入编写的axios文件,然后编写函数进行请求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="./axios.js"></script>
<title>axios</title>
</head>
<body>
<button type="button" onclick="getData()">axios</button>
<script>
// function getData() {
// axios({
// url: 'http://localhost:5005/data',
// method: 'GET'
// }).then(res => {
// console.log(res);
// })
// }
function getData() {
axios('http://localhost:5005/data').then(res => {
console.log(res);
})
}
</script>
</body>
</html>
打开浏览器,测试一下结果,perfect
然后是axios.method,源码对于axios.method的处理还是挺有意思的
通过遍历方法名来对Axios原型上添加方法
// 只需要url
const single = ['delete', 'get', 'head', 'options'];
// 可以发送请求体
const double = ['post', 'put', 'patch'];
single.forEach(method => {
Axios.prototype[method] = function () {
return this.request({
method,
url: arguments[0],
...arguments[1] || {}
})
}
})
double.forEach(method => {
Axios.prototype[method] = function () {
return this.request({
method,
url: arguments[0],
data: arguments[1] || {},
...arguments[2] || {}
})
}
})
这样就在原型上添加个get、post等方法,但是注意!!!我们导出的axios并不是Axios的实例,而是一个request方法,所以我们需要将Axios原型上的方法添加到导出的request上
写一个工具函数,负责转移方法
/**
* @description: 将第二个对象的自有属性添加到第一个属性
* @param {object} a 第一个对象
* @param {object} b 第二个对象
* @param {object} thisArg this指向
* @return {void}
*/
function extendUtil(a, b, thisArg) {
for (let key in b) {
if (b.hasOwnProperty(key)) {
if (typeof b[key] === 'function') {
a[key] = b[key].bind(thisArg);
} else {
a[key] = b[key]
}
}
}
}
再修改一下createInstance函数
function createAxiosInstance() {
const axios = new Axios();
let request = axios.request.bind(axios);
extendUtil(request, Axios.prototype, axios)
return request;
}
这样我们就将axios.method实现出来,再来测试一下
<script>
function getData() {
axios.get('http://localhost:5005/data').then(res => {
console.log(res);
})
}
</script>
再次成功
拦截器
拦截器也是axios的特色之一,可以在请求发出之前和接收到响应之后进行某些操作来处理数据。
拦截器的使用方式如下
// 添加请求拦截器
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);
});
通过使用方式我们可以看出,axios上还挂载着interceptors,其拥有request和response两个属性,通过use方法来添加拦截器
我们可以这样实现:创建一个InterceptorsManage构造函数,原型上添加use方法,axios的interceptors上挂载着两个这样的实例
function InterceptorsManage() {
this.handlers = [];
}
InterceptorsManage.prototype.use = function (fulfilled, rejected) {
this.handlers.push({
fulfilled,
rejected
})
}
function Axios(options) {
this.interceptors = {
request: new InterceptorsManage(),
response: new InterceptorsManage()
}
}
function createAxiosInstance() {
const axios = new Axios();
const request = axios.request.bind(axios);
extendUtil(request, Axios.prototype, axios);
extendUtil(request, axios);
return request;
}
现在我们已经实现了axios.interceptors.response.use这种结构,然后我们再来完善他的内部功能,首先我们把request的核心方法分离出来,为了方便拦截器的顺序调用
Axios.prototype.request = function (...args) {
let chain = [sendAjax.bind(this), undefined]
// 请求拦截
this.interceptors.request.handlers.forEach(interceptor => {
chain.unshift(interceptor.fulfilled, interceptor.rejected)
})
// 响应拦截
this.interceptors.response.handlers.forEach(interceptor => {
chain.push(interceptor.fulfilled, interceptor.rejected)
})
let promise = Promise.resolve(...args);
while (chain.length > 0) {
promise = promise.then(chain.shift(), chain.shift())
}
return promise;
}
拦截器两两一对,分别是操作和异常处理,将ajax请和undefined作为一对,请求拦截器的处理添加到ajax请求前面,响应拦截添加到后面,然后顺序执行,直到调用链中没有函数
let promise = Promise.resolve(...args);
用来传递参数,可以将处理结果一直传递到ajax请求,然后把ajax的响应作为参数继续传递给响应拦截,然后才把最终结果返回
我们再来测试一下,在html中添加拦截器
<script>
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
config.url += '?a=1'
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
console.log(response.name);
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
function getData() {
axios.get('http://localhost:5005/data').then(res => {
console.log(res);
})
}
</script>
在浏览器中看一下结果
大功告成