axios 是如何封装 HTTP 请求的
Axios 毋庸多说大家在前端开发中常用的一个发送 HTTP 请求的库,用过的都知道。本文用来整理项目中常用的 Axios 的封装使用。同时学习源码,手写实现 Axios 的核心代码。
Axios 常用封装
是什么
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。它的特性:
从浏览器中创建 XMLHttpRequests 从 node.js 创建 http 请求 支持 Promise API 拦截请求和响应 转换请求数据和响应数据 取消请求 自动转换 JSON 数据 客户端支持防御 XSRF 官网地址:http://www.axios-js.com/zh-cn/docs/#axios-config
Axios 使用方式有两种:一种是直接使用全局的 Axios 对象;另外一种是通过 axios.create(config) 方法创建一个实例对象,使用该对象。两种方式的区别是通过第二种方式创建的实例对象更清爽一些;全局的 Axios 对象其实也是创建的实例对象导出的,它本身上加载了很多默认属性。后面源码学习的时候会再详细说明。
请求
Axios 这个 HTTP 的库比较灵活,给用户多种发送请求的方式,以至于有些混乱。细心整理会发现,全局的 Axios(或者 axios.create(config)创建的对象) 既可以当作对象使用,也可以当作函数使用:
// axios 当作对象使用
axios.request(config)
axios.get(url[, config])
axios.post(url[, data[, config]])
// axios() 当作函数使用。 发送 POST 请求
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
});
后面源码学习的时候会再详细说明为什么 Axios 可以实现两种方式的使用。
取消请求
可以使用 CancelToken.source 工厂方法创建 cancel token:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).catch(function(thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// 处理错误
}
});
// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');
source 有两个属性:一个是 source.token 标识请求;另一个是 source.cancel() 方法,该方法调用后,可以让 CancelToken 实例的 promise 状态变为 resolved,从而触发 xhr 对象的 abort() 方法,取消请求。
拦截
Axios 还有一个奇妙的功能点,可以在发送请求前对请求进行拦截,对相应结果进行拦截。结合业务场景的话,在中台系统中完成登录后,获取到后端返回的 token,可以将 token 添加到 header 中,以后所有的请求自然都会加上这个自定义 header。
//拦截1 请求拦截
instance.interceptors.request.use(function(config){
//在发送请求之前做些什么
const token = sessionStorage.getItem('token');
if(token){
const newConfig = {
...config,
headers: {
token: token
}
}
return newConfig;
}else{
return config;
}
}, function(error){
//对请求错误做些什么
return Promise.reject(error);
});
我们还可以利用请求拦截功能实现 取消重复请求,也就是在前一个请求还没有返回之前,用户重新发送了请求,需要先取消前一次请求,再发送新的请求。比如搜索框自动查询,当用户修改了内容重新发送请求的时候需要取消前一次请求,避免请求和响应混乱。再比如表单提交按钮,用户多次点击提交按钮,那么我们就需要取消掉之前的请求,保证只有一次请求的发送和响应。
实现原理是使用一个对象记录已经发出去的请求,在请求拦截函数中先判断这个对象中是否记录了本次请求信息,如果已经存在,则取消之前的请求,将本次请求添加进去对象中;如果没有记录过本次请求,则将本次请求信息添加进对象中。最后请求完成后,在响应拦截函数中执行删除本次请求信息的逻辑。
// 拦截2 重复请求,取消前一个请求
const promiseArr = {};
instance.interceptors.request.use(function(config){
console.log(Object.keys(promiseArr).length)
//在发送请求之前做些什么
let source=null;
if(config.cancelToken){
// config 配置中带了 source 信息
source = config.source;
}else{
const CancelToken = axios.CancelToken;
source = CancelToken.source();
config.cancelToken = source.token;
}
const currentKey = getRequestSymbol(config);
if(promiseArr[currentKey]){
const tmp = promiseArr[currentKey];
tmp.cancel("取消前一个请求");
delete promiseArr[currentKey];
promiseArr[currentKey] = source;
}else{
promiseArr[currentKey] = source;
}
return config;
}, function(error){
//对请求错误做些什么
return Promise.reject(error);
});
// 根据 url、method、params 生成唯一标识,大家可以自定义自己的生成规则
function getRequestSymbol(config){
const arr = [];
if(config.params){
const data = config.params;
for(let key of Object.keys(data)){
arr.push(key+"&"+data[key]);
}
arr.sort();
}
return config.url+config.method+arr.join("");
}
instance.interceptors.response.use(function(response){
const currentKey = getRequestSymbol(response.config);
delete promiseArr[currentKey];
return response;
}, function(error){
//对请求错误做些什么
return Promise.reject(error);
});
最后,我们可以在响应拦截函数中统一处理返回码的逻辑:
// 响应拦截
instance.interceptors.response.use(function(response){
// 401 没有登录跳转到登录页面
if(response.data.code===401){
window.location.href = "http://127.0.0.1:8080/#/login";
}else if(response.data.code===403){
// 403 无权限跳转到无权限页面
window.location.href = "http://127.0.0.1:8080/#/noAuth";
}
return response;
}, function(error){
//对请求错误做些什么
return Promise.reject(error);
})
文件下载
通常文件下载有两种方式:一种是通过文件在服务器上的对外地址直接下载;还有一种是通过接口将文件以二进制流的形式下载。
第一种:同域名 下使用 a 标签下载:
// httpServer.js
const express = require("express");
const path = require('path');
const app = express();
//静态文件地址
app.use(express.static(path.join(__dirname, 'public')))
app.use(express.static(path.join(__dirname, '../')));
app.listen(8081, () => {
console.log("服务器启动成功!")
});
// index.html
<a href="test.txt" download="test.txt">下载</a>
这时候,我们可以将请求文件下载的逻辑进行封装。将二进制文件流存在 Blob 对象中,再将其转为 url 对象,最后通过 a 标签下载。
//封装下载
export function downLoadFetch(url, params = {}, config={}) {
//取消
const downSource = axios.CancelToken.source();
document.getElementById('downAnimate').style.display = 'block';
document.getElementById('cancelBtn').addEventListener('click', function(){
downSource.cancel("用户取消下载");
document.getElementById('downAnimate').style.display = 'none';
}, false);
//参数
config.params = params;
//超时时间
config.timeout = config.timeout ? config.timeout : defaultDownConfig.timeout;
//类型
config.responseType = defaultDownConfig.responseType;
//取消下载
config.cancelToken = downSource.token;
return instance.get(url, config).then(response=>{
const content = response.data;
const url = window.URL.createObjectURL(new Blob([content]));
//创建 a 标签
const link = document.createElement('a');
link.style.display = 'none';
link.href = url;
//文件名 Content-Disposition: attachment; filename=download.txt
const filename = response.headers['content-disposition'].split(";")[1].split("=")[1];
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
return {
status: 200,
success: true
}
})
}
https://juejin.cn/post/6878912072780873742
手写 Axios 核心代码
写了这么多用法终于到正题了,手写 Axios 核心代码。Axios 这个库源码不难阅读,没有特别复杂的逻辑,大家可以放心阅读
[广告]赞助链接:
关注数据与安全,洞悉企业级服务市场:https://www.ijiandao.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
随时掌握互联网精彩
- 因对组织心怀不满,勒索软件开发人员泄露Lockbit 3.0生成器
- 信阳师范学院回应学信网信息泄露:已报案,涉事学生干部被撤职
- “如果没有从事研发,学习自动化的我可能在装水表”
- 历史上的今天:第一台可编程机电子计算机诞生;硅谷先驱出生;饭否上线
- 专场招聘丨加入Qualcomm Modem团队,一起探索5G的更多可能
- .NET 6 的速度比 .NET 5 高出约 40%
- 一文get如何安全上云!
- 什么是文档签名证书?
- 程序员的传奇“破圈”之路
- 看雪课程 | 反勒索软件开发实战篇
- 斩获VCR竞赛榜第一,腾讯微视推出BLENDer单模型,超越多模型最好效果
- SSL如何使用非对称和对称加密?SSL加密技术