axios封装实例

一、axios需要封装哪些地方

封装axios主要关注一下几个方面:

  1. 创建实例
    创建实例的同时需要根据不同环境为axios配置不同的后台API地址,例如开发环境、生产环境、测试环境等。
    并且配置请求超时的时间
  2. 拦截request和response
    在request中添加用户Token,开启loading效果,删除重复的请求
    在response中关闭loading效果,清除请求缓存,根据返回的code处理错误信息。
  3. 处理异常状态码,超时异常,网络异常

其中删除重复请求是借助axios.CancelToken来实现的,这个CancelToken会向回调函数传入一个cancel函数作为参数,当调用这个cancel函数时,就可以取消掉之前的请求,因此只需要将cancel缓存起来即可。

二、封装axios代码

先定一个auth.js用来获取TOKEN:

1
2
3
4
const TOKEN_KEY = '__TOKEN'
export function getTokenAuth() {
return localStorage.getItem(TOKEN_KEY)
}

再定一个axios.js:

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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
import axios from 'axios'
import { ElLoading, ElMessage } from 'element-plus'
import { getTokenAuth } from '@/utils/auth.js'

const pendingMap = new Map()

const LoadingInstance = {
_target: null,
_count: 0
}

function MyAxios(axiosConfig, customOptions, loadingOptions) {
const service = axios.create({
// 根据不同的环境配置不同的后台API根地址
baseURL: process.env.NODE_ENV === 'production' ?
'http://prod.xxx.com' :
process.env.NODE_ENV === 'test' ?
'http://test.xxx.com':
'http://dev.xxx.com'
,
timeout: 10000
})

let custom_options = Object.assign(
{
repeat_request_cancel: true, // 是否开启取消重复请求
loading: false, // 是否开启loading层效果
reduct_data_format: true, // 是否开启简介数据结构
error_message_show: true, // 是否开启接口错误信息展示
code_message_show: false // 是否开启code不为0时的信息展示
},
customOptions
)

// 请求拦截
services.interceptors.request.use(
config => {
removePending(config)
custom_options.repeat_request_cancel && addPending(config)

// 创建loading实例
if (custom_options.loading) {
LoadingInstance._count ++
if (LoadingInstance._count === 1) {
LoadingInstance._target = ElLoading.service(loadingOptions)
}
}

// 自动携带Token
if (getTokenAuth() && typeof window !== 'undefined') {
config.headers.Authorization = getTokenAuth()
}

return config
},
error => {
return Promise.reject(error)
}
)

// 响应拦截
service.interceptor.response.use(
res => {
removePending(res.config)
custom_options.loading && closeLoading(custom_options)

// 响应码不为0时输出错误信息
if (
custom_options.code_message_show &&
response.data &&
response.data.code !== 0
) {
ElMessage({
type: 'error',
message: res.data.message
})
return Promise.reject(response.data)
}

return custom_options.reduct_data_format ? res.data: res
},
error => {
error.config && removePending(error.config)
custom_options.loading && closeLoading(custom_options)
custom_options.error_message_show && httpErrorStatusHandle(error)
return Promise.reject(error)
}
)

return service(axiosConfig)
}

export default MyAxios

// 处理异常状态码
function httpErrorStatusHandle(error) {
if (axios.isCancel(error)) return console.error('请求的重复请求:' + error.message)

let message = ''

if (error && error.message) {
switch(error.response.status) {
case 302:
message = '接口重定向!'
break
case 400:
message = '参数不正确'
break
case 401:
message = '您未登录,或者登录已经超时,请先登录!'
break
case 403:
message = '您没有权限操作'
break
case 404:
message = `请求地址错误:${error.responce.config.url}`
break
case 408:
message = '请求超时!'
break
case 409:
message = '系统已存在相同数据'
break
case 500:
message = '服务器内部错误'
break
case 501:
message = '服务未实现'
break
case 502:
message = '网关错误'
break
case 503:
message = '服务不可用'
break
case 504:
message = '服务暂时无法访问,请稍后再试'
break
case 505:
message = 'HTTP版本不受支持'
break
default:
message = '异常问题,请联系管理员'
}
}

if (error.message.includes('timeout')) message = '网络请求超时!'
if (error.message.includes('Network')) message = window.navigator.onLine ? '服务端异常!' : '您断网了!'

ElMessage({
type: 'error',
message
})
}

// 关闭Loading
function closeLoading(_options) {
if (_options.loading && LoadingInstance._count > 0) LoadingInstance._count --
if (LoadingInstance._count === 0) {
LoadingInstance._target.close()
LoadingInstance._target = null
}
}

// 储存请求回调
function addPending(config) {
const pendingKey = getPendingKey(config)
config.cancelToken =
config.cancelToken ||
new axios.CancelToken(cancel => {
if (!pendingMap.has(pendingKey)) {
pendingMap.set(pendingKey, cancel)
}
})
}

// 删除重复请求
function removePending(config) {
const pendingKey = getPendingKey(config)
if (pendingMap.has(pendingKey)) {
const cancelToken = pendingMap.get(pendingKey)
cancelToken(pendingKey)
pendingMap.delete(pendingKey)
}
}

function getPendingKey(config) {
let {url, method, params, data} = config
// 没看明白data为什么要转过来转回去
if (typeof data === 'string') data = JSON.parse(data)
// 这里是将config中的四个变量组合成一段字符串来作为请求缓存的key,实际开发我个人比较倾向于随机生成哈希码
return [url, method, JSON.stringify(params), JSON.stringify(data)].join('&')
}

三、实际使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// good.js
import MyAxios from './axios'

export function getListAPI(paramsList) {
return MyAxios({
url: '/api/list',
method: 'get'
})
}

export function postListAPI(paramsList) {
return MyAxios({
url: '/api/list',
method: 'post',
data: paramsList
})
}

参考:

77.9K 的 Axios 项目有哪些值得借鉴的地方
Axios 如何取消重复请求?
Axios 如何实现请求重试?
Axios 如何缓存请求数据?
封装Axios思路及代码


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!