![a5bb916d335458b2a72c09049bbde211.png](https://i-blog.csdnimg.cn/blog_migrate/f2836a7ed1fb0fb967a4a36d8ce83271.jpeg)
记录一个项目里用到的小东西,关于怎么写 ajax 的。
我要实现这个效果: 不需要处理任何服务端的错误, 不需要理会服务端的数据结构, 调用一个函数, 即可拿到我想要的结果。
import loadUser from '@/io/user'
async function task() {
let user = await loadUser(userCode)
}
项目里,我们一般使用 axios 这个库来做基础的 ajax 支持
基于 axios 的 api,已经可以实现一个很好的效果了, 但是前端还是不得不处理两个很烦人的东西
- 后端给的数据是被包裹的
- 后端会报错
比如一个常见的服务端响应结构:
{
"type": 0, //0 表示成功,-1表示失败
"data": null, // 成功了的话,这里放数据,失败了的话这里什么都没有
"message": "" // 失败了的话这里放失败的具体信息,成功了的话这里什么都没有
}
先不提应该尽量使用 rest api 来处理这些异常的问题了, 比如我见过一份规范文档里头,因为 tomcat 的某个版本的 patch 方法有安全问题,然后全局把 patch/delete 这些方法都禁用掉了
根据公司不同,规范文档不同,这里的数据结构也不尽相同
但是按照传统的做法,这里的代码还是很冗长,繁琐
loadUser()
.then(response => {
if (response.type == 0) {
let user = response.data
} else {
showMessage(response.message)
}
})
.catch(error => {
if (error.response.statusCode > 500) {
showError('服务端错误')
} else {
//.....
}
})
即使我们用上异步函数, 这里的代码还是会很繁琐
所以我的做法是充分利用 axios.interceptors
let baseUrl = '/web-context' // axios 有一个 baseURL 选项,不这么用是个人习惯,为了更灵活,不是所有的url都需要这个
axios.interceptors.request.use(config => {
if (!config.url.startsWith('http')) {
config.url = `${baseUrl}${config.url}`
}
return config
})
function handleResult(data) {
// 这一段是根据具体项目,可以随意变动的,在组织项目的时候,可以让这一部分被传递进来
if (data.type == 0) {
return data.data
} else {
throw new Error(data.message)
}
}
function showMessage(httpCode, config, res) {
// 这里也是项目技术决定的,如何显示异常信息,这个函数的具体实现,也可以被传递进来
if (!config.opt.showMessage) {
return
}
if (httpCode == 200) {
// 服务端主动抛出来的错误
showInfo(res.message)
} else if (httpCode > 500) {
showError('服务端内部错误')
} else if (httpCode > 400) {
showError('客户端错误')
} else {
showError('未知错误')
}
}
function encrypt(res) {
if (process.env.NODE_ENV == 'development') {
return res
}
// 同样的,可以根据具体项目随意的变动
return res.code // 由于这里的值可能会被直接打印在控制台上,所以可以处理一下,不将敏感信息直接打印出来
}
axios.intercepors.response.use(
res => {
if (res.status == 200) {
// 为了简单起见, 这里会在构建请求方法的时候,保证这里的opt选项,不需要多做一次判断
// 可以由接口定义处来处理这里的设置,完全不处理响应体,可以保证兼容项目里所有的接口响应格式
// 防止部分接口响应格式不统一的问题
if (res.config.opt.handleResult) {
try{
let result = await handleResult(res.data)
return Promise.resolve(result)
}catch(e){
showMessage(200,res.config, res.data)
return Promise.reject(e)
}
}
return res.data
}
showMessage(200, res.config, res.data)
return Promise.reject(new Error(encrypt(res.data)))
},
error => {
let { response } = error
if (response.status >= 500) {
message(response.data, 'error')
} else if (response.status >= 400) {
message(response.data, 'warning')
}
showMessage(response.status, error.config, response.data)
return Promise.reject(new Error(encrypt(response.data)))
}
)
基于以上代码, 即可做到基本的请求处理, 可以实现自动处理异常,自动处理提示信息, 对于业务来说, 代码已经简化成了这样
async function task() {
try {
let user = await axios({
url: '/load-user',
method: 'get',
param: { code: '1234' },
opt: {
handleResult: true,
showMessage: true
}
})
} catch (e) {
// 所有在 interceptor.response 中 reject 的分支,都会进入这里, 如果默认效果即可,则不需要写这一层catch
// 如果这里没有catch, 则会被算作未捕获的异常,直接会打印到控制台
}
}
但是这东西还是一大坨, 每次调用都写一坨, 很别扭,很难看, 而且,通信接口这东西,一旦定下来了,那么 url,method 这些,都是固定的,每次调用都一毛一样, 那么再进行一次重构
async function loadUser(code, opt = {}) {
return axios({
url: '/load-user',
method: 'get',
param: {
code
},
opt: {
//是否需要调用自定义的结果处理函数,一般来说,一个项目的响应格式是统一的,都应该调用,但是不排除有部分接口是不统一的,允许自行配置,不处理这个响应体
handleResult: true,
// 是否调用通用的提示信息函数,一般来说,都是需要的,但是不排除部分接口的提示信息会由接口调用处来处理,这里允许调用处将这个配置关闭
// 从技术上来说,是可以判断编程人员是否catch到异常的,但是写了catch不一定是要做信息提示,可能只是为了恢复程序流程,保证能够继续执行,所以这里的提示是否开启,还是需要一个单独的配置来做
showMessage: true,
...opt
}
})
}
再来, 如果我们写了好多接口,就会发现,这里绝大部分的东西还是一致的, 比如如果都是 get 请求,那么基本只有 url 会有区别,其他内容完全一致
再重构一次
function genGetInterface(url) {
return function(code, opt = {}) {
return axios({
url,
method: 'get',
param: { code },
opt: {
handleResult: true,
showMessage: true,
...opt
}
})
}
}
const loadUser = genGetInterface('/load-user')
const loadRole = genGetInterface('/load-role')
对于一些不太统一的接口
const loadSth = async data => {
return axios({
url: '/load-sth',
method: 'post',
data,
opt: {
handleResult: false,
showMessage: false
}
})
}
以上,即可实现一个效果还不从的 ajax 实现模式 -- 前提是项目里绝大部分的接口很规范统一,如果接口都千奇百怪的,就不要考虑怎么处理这些了,先去把后端教训一顿吧
最后,如果项目遵守 rest 规范
那么可以得到这么一份生成器
function genGet(url) {
return (code, opt) =>
axios({
url: `${url}/${code}`,
method: 'GET',
opt
})
}
function genGetList(url) {
return (pageSize, pageNo, searchParam, opt) =>
axios({
url,
method: 'GET',
params: {
pageSize,
pageNo,
searchParam
},
opt
})
}
function genCreate(url) {
return (model, opt) =>
axios({
url,
method: 'POST',
data: model,
opt
})
}
function genUpdate(url) {
return (code, model, opt) =>
axios({
url: `${url}/${code}`,
method: 'PATCH',
data: model,
opt
})
}
function genDelete(url) {
return (codes, opt) =>
axios({
url,
method: 'DELETE',
data: codes,
opt
})
}