Axios封装
howcode 2022-12-30 0 javascript
# 简介
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。虽然,Axios是个优秀的 HTTP 库,但是,直接在项目中使用并不是那么方便,会存在大量的重复性方法和代码,所以,我们需要对其进行一定程度上的配置封装,减少重复代码,精简调用方式。
本次使用 axios 版本为 ^0.21.4
Axios 文档地址传送门 ☞ Axios 文档
# 设计
先设计下我想要这个通用请求能达到什么样的效果:
- 优化配置,设置默认配置项(responseType、跨域携带cookie、token、超时设置)
- 统一设置请求头
- 根据环境设置 baseURL
- 通过 Axios 方法直接发起请求
- 添加请求拦截器
- 添加响应拦截器
- 导出 Primise 对象
- 封装 Post 方法,精简 post 请求方式
- 封装 Get 方法,精简 get 请求方式
- 请求成功,配置业务状态码
- 全局的loading配置 下面👇是 JS 版本
# 封装主体 Axios
// src/api/axios.js
import axios from "axios";
import Qs from 'qs'
export const Axios = (url,method='get',params={},headers={})=>{
// 根据 定义的环境状态,切换不同的 baseURL 开发环境使用代理, 生产环境可以直接使用域名全拼
const BaseUrl = process.env.NODE_ENV==='development'? '' : process.env.BASEURL;
let defaultHeaders = {
'Content-Type': 'application/json;charset=UTF-8',
// 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', // 指定提交方式为表单提交 或上传
// 'Content-Type' :'multipart/form-data;charset=UTF-8',
'Accept': 'application/json', // 通过头指定,获取的数据类型是JSON 'application/json, text/plain, */*',
// 'Access-Control-Allow-Origin': 'true',
// 'Access-Control-Allow-Credentials': 'true',
}
if(headers){
for (let i in headers) {
defaultHeaders[i] = headers[i];
}
}
const showResState = (state) => {
let message = ''
// 这里只做部分常见的示例,具体根据需要进行配置
switch (state) {
case 400:
message = '请求错误(400)'
break
case 401:
message = '未授权,请重新登录(401)'
break
case 403:
message = '拒绝访问(403)'
break
case 404:
message = '请求出错(404)'
break
case 500:
message = '服务器错误(500)'
break
case 501:
message = '服务未实现(501)'
break
case 502:
message = '网络错误(502)'
break
case 503:
message = '服务不可用(503)'
break
default:
message = `连接出错(${state})!`
}
return `${message},请检查网络或联系网站管理员!`
}
// 添加请求拦截器
axios.interceptors.request.use( (config) => {
// 在发送请求之前做些什么
console.log(config)
// header 配置 Token 判断Token是否过期 没过期则正常处理 过期则发起刷新Token的请求 拿到新的Token保存
config.headers.Authorization = null;
// const token = !localStorage.getItem('__auth_provider_token__')?localStorage.setItem('__auth_provider_token__',''):localStorage.getItem('__auth_provider_token__');
// let navigate = useNavigate();
// if(sessionStorage.getItem("__auth_provider_isLogin__") !== '1' && isAuth ){//&& !token
// alert('token失效');
// navigate('/login');
// return new Promise((resolve, reject) => {});
// }
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use((res) => {
// 对响应数据做点什么
const status = res.status;
let msg = ''
if (status < 200 || status >= 300) {
// 处理http错误,抛到业务代码
msg = showResState(status)
if (typeof res.data === 'string') {
res.data = { msg }
} else {
res.data.msg = msg
}
}
return res;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
// 1. 执行异步ajax请求
const instance = axios({
// `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
// 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
baseURL: BaseUrl,
// `url` 是用于请求的服务器
url: url,
// `method` 是创建请求时使用的方法
method: method || 'get',
// mode: 'cors',
// cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
// `headers` 是即将被发送的自定义请求头
headers: {...defaultHeaders},
// `transformRequest` 允许在向服务器发送前,修改请求数据
// 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法
// 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream
transformRequest: [function (data, headers) {
// 对 data 进行任意转换处理
return data;
}],
// `transformResponse` 在传递给 then/catch 前,允许修改响应数据
transformResponse: [function (data) {
// 对 data 进行任意转换处理
return data;
}],
// `params` 是即将与请求一起发送的 URL 参数
// 必须是一个无格式对象(plain object)或 URLSearchParams 对象
params: method === 'get' ? params || {} : {},
// `paramsSerializer` 是一个负责 `params` 序列化的函数
paramsSerializer: function(params) {
return Qs.stringify(params, {arrayFormat: 'brackets'})
},
// `data` 是作为请求主体被发送的数据
// 只适用于这些请求方法 'PUT', 'POST', 和 'PATCH'
// 在没有设置 `transformRequest` 时,必须是以下类型之一:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - 浏览器专属:FormData, File, Blob
// - Node 专属: Stream
data: method === 'post' ? params || {} : {},
// `timeout` 指定请求超时的毫秒数(0 表示无超时时间)
// 如果请求话费了超过 `timeout` 的时间,请求将被中断
timeout: 0,
// `withCredentials` 表示跨域请求时是否需要使用凭证
withCredentials: false, // default 为true则产生跨域,跨域携带cookie
// `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
responseType: 'json', // default
});
return new Promise((resolve, reject) => {
instance.then(response => {
// 2. 如果成功了, 调用resolve(value)
resolve(response);
})
.catch(error => {
// 3. 如果失败了, 不调用reject(reason), 而是提示异常信息
reject(error)
}).finally(() => {
})
});
}
// GET 请求 get 下 params 为查询参数
export const Get = (url,params={},headers={}) => {
return Axios(url,'get',params,headers)
}
// POST 请求 post 下 params 为body参数, 如果 post 下既需要传查询参数也需要传实体参数,则查询参数配置在 url 中
export const Post = (url,params={},headers={}) => {
return Axios(url,'post',params,headers)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# 拦截器
在请求或响应被 then
或 catch
处理前拦截它们。
//添加请求拦截器
axios.interceptors.request.use( (config) => {
// 在发送请求之前做些什么
console.log(config)
// header 配置 Token 判断Token是否过期 没过期则正常处理 过期则发起刷新Token的请求 拿到新的Token保存
config.headers.Authorization = null;
// const token = !localStorage.getItem('__auth_provider_token__')
//?localStorage.setItem('__auth_provider_token__','')
//:localStorage.getItem('__auth_provider_token__');
// let navigate = useNavigate();
// if(sessionStorage.getItem("__auth_provider_isLogin__") !== '1' && isAuth && !token){
// alert('token失效');
// navigate('/login');
// return new Promise((resolve, reject) => {});
// }
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use((res) => {
// 对响应数据做点什么
return res;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 状态处理
在响应拦截中,如果有需要还可以对状态码提示进行处理。现在项目一般后端都会给处理好,这个就根据自己的项目情况进行配置吧。
//根据不同的状态码,生成不同的提示信息
const showResState = (state) => {
let message = ''
// 这里只做部分常见的示例,具体根据需要进行配置
switch (state) {
case 400:
message = '请求错误(400)'
break
case 401:
message = '未授权,请重新登录(401)'
break
case 403:
message = '拒绝访问(403)'
break
case 404:
message = '请求出错(404)'
break
case 500:
message = '服务器错误(500)'
break
case 501:
message = '服务未实现(501)'
break
case 502:
message = '网络错误(502)'
break
case 503:
message = '服务不可用(503)'
break
default:
message = `连接出错(${state})!`
}
return `${message},请检查网络或联系网站管理员!`
}
// 添加响应拦截器
axios.interceptors.response.use(function (res) {
// 对响应数据做点什么
const status = res.status
let msg = ''
if (status < 200 || status >= 300) {
// 处理http错误,抛到业务代码
msg = showResState(status)
if (typeof res.data === 'string') {
res.data = { msg }
} else {
res.data.msg = msg
}
}
return res;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 封装 Get 请求
//GET 请求 get 下 params 为查询参数
export const Get = (url,params={},headers={}) => {
return Axios(url,'get',params,headers)
}
1
2
3
4
2
3
4
# 封装 Post 请求
// POST 请求 post 下 params 为body参数, 如果 post 下既需要传查询参数也需要传实体参数,则查询参数配置在 url 中
// post 请求支持上传文件
export const Post = (url,params={},headers={}) => {
return Axios(url,'post',params,headers)
}
1
2
3
4
5
2
3
4
5
# 封装 API
// src/api/index.js
import {Axios,Post,Get} from './axios'; // Axios 数据请求方法
/**
* 使用说明:
* import { login } from '@/api/index'
* 使用:
* login(params).then((res)=>{
* // 业务处理
* }).catch((err)=>{
* console.log(err)
* });
*/
/**
* 请求示例--Get 示例
* @params title string
* {title: 'cp'}
*/
export const getArticles = (params={}) => {
return Get('/api/v1/blog/getArticles', params);
}
/**
* 请求示例--Post 示例
* @params code string
* {code: 'xxxxxxx'}
*/
export const saveArticles = (params={}) => {
return Post('/api/v1/blog/saveArticles', params);
}
/**
* Axios 示例
* @params title string
* {title:'标题'}
*/
export const getAxiosDemo = (params={}) => {
return Axios('/api/v1/blog/getArticles','get', params);
}
/**
* 请求示例--Post 上传示例
* @params file object
* file
*/
export const upload = (params={}) => {
return Post('/api/v1/article/upload', params);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 使用
// 直接在页面中使用封装的方法
import { Axios, Get, Post } from '@/api'
// 使用 Axios 配置
Axios(
'/api/v1/blog/getArticles',
'get',
{id:123}
)
.then((res: any) => {
console.log(res)
}).catch((err:any)=>{
console.log(err)
});
// 使用 Get 请求
Get(
'/api/v1/blog/getArticles',
'get',
{id:123}
)
.then((res) => {
console.log(res)
}).catch((err)=>{
console.log(err)
});
// 使用 Post 请求
Post(
'/api/v1/blog/getArticles',
{id:123}
)
.then((res) => {
console.log(res)
}).catch((err)=>{
console.log(err)
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
对于在实际业务系统中,可以 对 Api 进行封装,放在一个或一组文件中,然后在页面中通过 API 接口名称进行调用,这样也便于管理 api 地址。对比下这种方式是不是很方便呢。
// 在页面中使用封装好的 API
import { getArticles } from '@/api'
getArticles({id:123}).then((res: any) => {
console.log(res)
}).catch((err:any)=>{
console.log(err)
});
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 上传
这里单独把上传文件的代码也贴出来,供参考
<script setup>
import { ref } from 'vue';
const fileRef = ref(null);
const handleUploadBtn = () => {
fileRef.value.click();
}
const handleUpload = () => {
let file = fileRef.value.files[0];
if (file.size > 10 * 1024 * 1024) {
// 文件大小超限了
alert('请上传小于10M的图片');
fileRef.value.value = ''; // 清空内容
return;
}
let forms = new FormData();
forms.append('file', file);
// forms.append('filePath', `pc/client-${moment().format('YYYY-MM-DD')}/`);
fileRef.value.value = ''; // 清空内容
upload(forms).then((res) => {
console.log(res);
}).catch((err)=>{
console.log(err);
});
}
</script>
<template>
<input
type="file"
accept="image/*"
ref="fileRef"
@change="handleUpload"
/>
<button @click="handleUploadBtn">选择文件</button>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
评论
- 表情
——暂无评论——