axios的二次封装
为什么封装
随着项目规模增大,如果每发起一次HTTP
请求,就要把这些比如设置超时时间、设置请求头、根据项目环境判断使用哪个请求地址、错误处理等等操作,都需要写一遍
基本使用
调用型和方法型
---------发送请求
axios.get('/data.json',{
params:{
id:12
}
}).then((res)=>{
console.log(res)
})
axios({
method:'get',
url:'/data.json',
params:{
id:12
}
}).then((res)=>{
console.log(res)
})
post方法有三个参数,分别是url、数据、config参数。
并发请求axios.all([])
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
axios.all([getUserAccount(), getUserPermissions()])
.then(axios.spread(function (res1, res2) {
// res1第一个请求的返回的内容,res2第二个请求返回的内容
// 两个请求都执行完成才会执行
}));
基础封装
axios文件封装在目录src/utils/https.js
,对外暴露callApi
函数
设置接口请求前缀
1、环境区分
利用node
环境变量来作判断,用来区分开发、测试、生产环境
callApi
函数暴露prefixUrl
参数,用来配置api url前缀
,默认值为api
// src/utils/https.js
import axios from 'axios'
export const callApi = ({
url,
...
prefixUrl = 'api'
}) => {
if (!url) {
const error = new Error('请传入url')
return Promise.reject(error)
}
const fullUrl = `/${prefixUrl}/${url}`
...
return axios({
url: fullUrl,
...
})
}
// vue.config.js
const targetApi1 = process.env.NODE_ENV === 'development' ? "http://www.kaifa1.com" : "http://www.ceshi1.com"
const targetApi2 = process.env.NODE_ENV === 'development' ? "http://www.kaifa2.com" : "http://www.ceshi2.com"
module.exports = {
devServer: {
proxy: {
'/api1': {
target: targetApi1,
changeOrigin: true,
pathRewrite: {
'/api1': ""
}
},
'/api2': {
target: targetApi2,
changeOrigin: true,
pathRewrite: {
'/api2': ""
}
},
}
}
}
2、设置请求头与超时时间
常见以下三种:
(1)application/json
参数会直接放在请求体中,以JSON格式的发送到后端。这也是axios请求的默认方式。这种类型使用最为广泛。
{a:123 ,b:'fhoi' , c:3 }
(2)application/x-www-form-urlencoded
请求体中的数据会以普通表单形式(键值对)发送到后端。
{"a":123 ,"b":'fhoi' , "c":3 }
(3)multipart/form-data
参数会在请求体中,以标签为单元,既可以上传键值对,也可以上传文件。通常被用来上传文件的格式。
callApi
函数暴露contentType
参数,用来配置请求头
,默认值为application/json; charset=utf-8
通过options
配置headers
,写n遍headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'}
;而通过contentType
配置,传参json || urlencoded || multipart
即可
// src/utils/https.js
import axios from 'axios'
import qs from 'qs' //处理urlencoded模式 将其转化
const contentTypes = {
json: 'application/json; charset=utf-8',
urlencoded: 'application/x-www-form-urlencoded; charset=utf-8',
multipart: 'multipart/form-data',
}
const defaultOptions = {
headers: {
Accept: 'application/json',
'Content-Type': contentTypes.json,
}
}
export const callApi = ({
url,
data = {},
options = {},
contentType = 'json', // json || urlencoded || multipart
prefixUrl = 'api'
}) => {
...
const newOptions = {
...defaultOptions,//默认值
...options,//传入参数覆盖默认值
headers: {
'Content-Type': options.headers && options.headers['Content-Type'] || contentTypes[contentType],
},
}
const { method } = newOptions //结构出方法参数
if (method !== 'get' && method !== 'head') {
if (data instanceof FormData) {//formdata格式 一般文件
newOptions.data = data
newOptions.headers = {
'x-requested-with': 'XMLHttpRequest',
'cache-control': 'no-cache',//跳过强缓存
}
} else if (options.headers['Content-Type'] === contentTypes.urlencoded) {
newOptions.data = qs.stringify(data)//键值对要用qs处理
} else {
Object.keys(data).forEach((item) => {//删除不存在的参数
if (
data[item] === null ||
data[item] === undefined ||
data[item] === ''
) {
delete data[item]
}
})
// 没有必要,因为axios会将JavaScript对象序列化为JSON
// newOptions.data = JSON.stringify(data);
}
}
return axios({ //这tm才是核心代码
url: fullUrl,
...newOptions,
})
}
封装请求方法
响应结果处理
// src/utils/https.js
import axios from 'axios'
import { Message } from "element-ui";
export const callApi = ({
...
}) => {
...
return axios({
url: fullUrl,
...newOptions,
})
.then((response) => {
const { data } = response
if (data.code === 'xxx') {
// 与服务端约定
// 登录校验失败
} else if (data.code === 'xxx') {
// 与服务端约定
// 无权限
router.replace({ path: '/403' })
} else if (data.code === 'xxx') {
// 与服务端约定
return Promise.resolve(data)
} else {
const { message } = data
if (!errorMsgObj[message]) {
errorMsgObj[message] = message
}
setTimeout(debounce(toastMsg, 1000, true), 1000)
return Promise.reject(data)
}
})
.catch((error) => {
if (error.response) {
const { data } = error.response
const resCode = data.status
const resMsg = data.message || '服务异常'
// if (resCode === 401) { // 与服务端约定
// // 登录校验失败
// } else if (data.code === 403) { // 与服务端约定
// // 无权限
// router.replace({ path: '/403' })
// }
if (!errorMsgObj[resMsg]) {
errorMsgObj[resMsg] = resMsg
}
setTimeout(debounce(toastMsg, 1000, true), 1000)
const err = { code: resCode, respMsg: resMsg }
return Promise.reject(err)
} else {
const err = { type: 'canceled', respMsg: '数据请求超时' }
return Promise.reject(err)
}
})
}
完整:
// src/utils/https.js
import axios from 'axios'
import qs from 'qs'
import { debounce } from './debounce'
const contentTypes = {
json: 'application/json; charset=utf-8',
urlencoded: 'application/x-www-form-urlencoded; charset=utf-8',
multipart: 'multipart/form-data',
}
function toastMsg() {
Object.keys(errorMsgObj).map((item) => {
Message.error(item)
delete errorMsgObj[item]
})
}
let errorMsgObj = {}
const defaultOptions = {
withCredentials: true, // 允许把cookie传递到后台
headers: {
Accept: 'application/json',
'Content-Type': contentTypes.json,
},
timeout: 15000,
}
export const callApi = ({
url,
data = {},
method = 'get',
options = {},
contentType = 'json', // json || urlencoded || multipart
prefixUrl = 'api',
}) => {
if (!url) {
const error = new Error('请传入url')
return Promise.reject(error)
}
const fullUrl = `/${prefixUrl}/${url}`
const newOptions = {
...defaultOptions,
...options,
headers: {
'Content-Type':
(options.headers && options.headers['Content-Type']) ||
contentTypes[contentType],
},
method,
}
if (method === 'get') {
newOptions.params = data
}
if (method !== 'get' && method !== 'head') {
newOptions.data = data
if (data instanceof FormData) {
newOptions.headers = {
'x-requested-with': 'XMLHttpRequest',
'cache-control': 'no-cache',
}
} else if (newOptions.headers['Content-Type'] === contentTypes.urlencoded) {
newOptions.data = qs.stringify(data)
} else {
Object.keys(data).forEach((item) => {
if (
data[item] === null ||
data[item] === undefined ||
data[item] === ''
) {
delete data[item]
}
})
// 没有必要,因为axios会将JavaScript对象序列化为JSON
// newOptions.data = JSON.stringify(data);
}
}
axios.interceptors.request.use((request) => {
// 移除起始部分 / 所有请求url走相对路径
request.url = request.url.replace(/^\//, '')
return request
})
return axios({
url: fullUrl,
...newOptions,
})
.then((response) => {
const { data } = response
if (data.code === 'xxx') {
// 与服务端约定
// 登录校验失败
} else if (data.code === 'xxx') {
// 与服务端约定
// 无权限
router.replace({ path: '/403' })
} else if (data.code === 'xxx') {
// 与服务端约定
return Promise.resolve(data)
} else {
const { message } = data
if (!errorMsgObj[message]) {
errorMsgObj[message] = message
}
setTimeout(debounce(toastMsg, 1000, true), 1000)
return Promise.reject(data)
}
})
.catch((error) => {
if (error.response) {
const { data } = error.response
const resCode = data.status
const resMsg = data.message || '服务异常'
// if (resCode === 401) { // 与服务端约定
// // 登录校验失败
// } else if (data.code === 403) { // 与服务端约定
// // 无权限
// router.replace({ path: '/403' })
// }
if (!errorMsgObj[resMsg]) {
errorMsgObj[resMsg] = resMsg
}
setTimeout(debounce(toastMsg, 1000, true), 1000)
const err = { code: resCode, respMsg: resMsg }
return Promise.reject(err)
} else {
const err = { type: 'canceled', respMsg: '数据请求超时' }
return Promise.reject(err)
}
})
}
具体使用:
const getapi =()=>{
callApi({
url: '123/upload',
contentType: 'multipart',
})
}