「这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战」
前言
网上有很多请求封装的文章,可以通过对请求的拦截,对请求参数、请求头、返回值等进行修改,甚至取消请求,或是重发请求,请求参数加密,鉴权等等,基本每个成熟的项目都会对请求进行封装,方案很多,个人比较喜欢的一种采用hook对请求的拦截做各种处理,这里就不多谈这个了,这里我们来谈谈对接口调用方式的封装。
常见调用方法
没做封装版
以axios为例,直接使用post\get\delete三种方式,调用接口,看看这段代码:
axios.post(url, {data}).then(res=>{})
这么写,url每个页面都得写一次,万一后台链接换了,改起不麻烦?即便是你能全局替换,但后面维护的人,维护同样的功能,不同页面时,得把这代码复制得到处都是。
进阶版
在前面的基础上将请求链接统一写在一个js文件里,在使用的时候通过引入的方式来使用
// static.js
export default {
aLink: 'xxx',
bLink: 'ccc'
}
// 调用的地方
import url from 'static.js'
axios.post(url.aLink, {data}).then(res=>{})
看代码,其实没多大变化,代码反而多了一点点
来看看这个版
其实每个请求都是一个promise,如果把这个promise统一弄到一处,这样调用的地方,只要引入就好,前面那段代码就不会重复了。来个例子:
// loginApi.js
const S_URL = {
login: '/api/login',
reg: '/api2/reg'
}
export login () {
return axios.post(S_URL.login, {data})
}
// 调用的地方
import {login} from 'loginApi.js'
login.then(res=>{})
这版没毛病按需加载,也做到了统一管理,要改的时候改一处就好。但奈何本人有点懒,每个调用地方都得引入一次,一个页面接口一堆,不乐意一个个引入,所以个人爱好版来了。
个人爱好版
不喜欢一个个引入,那就一次性全局引入,然后把他们挂载到全局变量上去,可以是vue对象,也可以是window, global,我是挂到vue对象上。
来看代码
// storeApi.js
export default {
// 详情查询
storeCouponInfo: {
method: 'post',
url: '/stores/stores/coupon/storeCouponInfo',
// 这里还可有一些其他配置,比如是否需要统一的报错处理等
},
}
这里可以把所有apis文件夹下的通过js方法一起导入(不清楚的可以看看 require.context)
// api.js
import store from './apis/storeApi.js'
import MyHttp from './req'
const ALL_API = Object.assign({}, storeApi)
const api = new MyHttp({}, ALL_API)
export default api
这里有一些处理在req.js里面
// req.js
import axios from 'axios'
let axiosInstance
let typeOf = typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol' ? function (obj) {
return typeof obj
} : function (obj) {
return obj && typeof Symbol === 'function' && obj.constructor === Symbol && obj !== Symbol.prototype ? 'symbol' : typeof obj
}
/*存储请求的数组*/
let refreshSubscribers = []
window.isRefreshing = false
/*将所有的请求都push到数组中,其实数组是[function(token){}, function(token){},...]*/
function subscribeTokenRefresh(cb) {
refreshSubscribers.push(cb);
}
/*数组中的请求得到新的token之后自执行,用新的token去请求数据*/
function onRefreshed(token) {
console.log('onRefreshedvvvvvv--->', refreshSubscribers)
refreshSubscribers.map(cb => cb(token));
}
// 封装请求
class MyRequest {
constructor() {
axiosInstance = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 30000,
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
}
})
// 添加请求拦截器
axiosInstance.interceptors.request.use(function (config) {
console.log('config--->', config)
let { isLoading } = config.options
if (config.method === 'get' && config.data) {
config.params = config.data
}
// 处理请求Loading
if (isLoading) {
global.toast.loading({
message: '加载中...',
forbidClick: true,
duration: 30000
})
}
return config
}, function (error) {
return Promise.reject(error)
})
// 添加响应拦截器
axiosInstance.interceptors.response.use(
(response) => {
global.toast.clear()
if (!response || !response.data) return Promise.reject(response)
let res = response.data
let _this = this
switch (res.code) {
case 0:
return res
case 2000:
case 2007:
switch (response.config.url) {
case '/generateTokenByCustom':
return Promise.reject(res)
default:
var userInfo = global.lStore.get(global.lStore.staticName.USER_INFO)
if (userInfo && userInfo.id) {
// 防止多个请求需要token的问题
if (!window.isRefreshing) {
window.isRefreshing = true
global.api.generateTokenByCustom({ accountId: userInfo.id }, { dealException: true }).then(result => {
try {
//储存token
global.token = result.data.token
global.addon.refreshToken({
token: result.data.token
})
// 重新请求
onRefreshed(result.data.token)
window.isRefreshing = false
} catch (error) {
console.log(error)
global.toast('登录失败,请关闭APP重试')
return Promise.reject(error)
}
})
}
let retry = new Promise((resolve) => {
/*(token) => {...}这个函数就是cb*/
subscribeTokenRefresh((token) => {
let { url, method, data, headers, options } = response.config
data = JSON.parse(data)
headers.token = "Bearer " + token
/*将请求挂起*/
resolve(_this.sendRrquest(url, method, data, headers, options))
})
})
return retry
}
break
}
break
default:
if (res.message !== 'success') {
console.log('err:', res)
let { dealException } = response.config.options || {}
if (!dealException) {
global.toast(res.message || '服务忙,请稍后再试')
}
return Promise.reject(res)
}
break
}
}, error => {
global.toast.clear()
console.log('error: ', error)
global.toast('服务忙,请稍后再试')
return Promise.reject(error)
})
}
// 是否是对象
isObject(obj) {
return (typeof obj === 'undefined' ? 'undefined' : typeOf(obj)) === 'object' && obj !== null
}
// 继承扩展
extend(obj) {
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key]
}
if (this.isObject(obj) && args.length > 0) {
if (Object.assign) {
return Object.assign.apply(Object, [obj].concat(args))
}
args.forEach(function (arg) {
if (this.isObject(arg)) {
Object.keys(arg).forEach(function (key) {
obj[key] = arg[key]
})
}
})
}
return obj
}
sendRrquest(url, method, data, header, options) {
return axiosInstance({
method: method,
url: url,
data: data,
headers: header,
options
})
}
}
let myRequest = new MyRequest()
// 出请求
let MyHttp = function (defaultParams, ALL_API) {
let resource = {}
for (let actionName in ALL_API) {
let _config = ALL_API[actionName]
// options为对象包含(pathParam:url参数,dealException:是否单独处理错误信息, isLoading:是否显示Loading,contentType请求类型)
resource[actionName] = (pdata, options = {}) => {
let paramsData = myRequest.extend({}, defaultParams, pdata)
let url = _config.url
if (options.pathParam) {
url = url + options.pathParam
}
// 头信息
let head = { 'Content-Type': options.contentType || 'application/json;charset=UTF-8' }
switch (url) {
case '/generateTokenByCustom':
delete head.token
return myRequest.sendRrquest(url, _config.method, paramsData, head, options)
default:
if (global.token) {
head.token = "Bearer " + global.token
// console.log(head.token)
}
else{
delete head.token
}
return myRequest.sendRrquest(url, _config.method, paramsData, head, options)
}
}
}
return resource
}
export default MyHttp
看看MyHttp这个方法,我们所有请求都是要走这里,这里对所请求还做了前置处理,具体需要看各位的需求。
最后绑定就简单了,直接在main.js里面处理就好,随你喜欢绑哪里
global.api = api
Vue.prototype.api = api
最最后,我传播的是偷懒的思想,也欢迎更舒服的方案分享探讨。