上节, 前后端交互功能基本实现, 但在实际工程项目中这其实还不够。
前端捕捉(怼)后端错误
就拿项目中目前还存留的bug举例:
在后端代码未启动的情况下,先运行前端服务,会返还如下错误
这是因为后端未返回数据,导致的语法错误
因此, 会产生三个问题
1.程序无法运行
2.页面不美观
3.不利排查原因
所以这里需要在不影响程序运行的前提下, 将错误以弹窗的形式展示出来,
例如:
这样不管是对开发人员,还是用户都更加友好
具体做法:
在ajax里做一层 状态码的判断
const ajax = function(request) {
var r = new XMLHttpRequest()
r.open(request.method, request.url, true)
if (request.contentType !== undefined) {
r.setRequestHeader('Content-Type', request.contentType)
}
r.onreadystatechange = function(event) {
if(r.readyState === 4) {
+ if(r.status == 200) {
+ const data = JSON.parse(r.response)
+ request.success(data)
+ }
+ if(r.status == 500) {
+ request.error()
+ }
}
}
if (request.method === 'GET') {
r.send()
} else {
r.send(request.data)
}
}
//用Promise封装原生ajx
const ajaxPromise = function(url, method, form) {
var p = new Promise((resolve, reject) => {
const request = {
url: url,
method: method,
contentType: 'application/json',
success: function(r) {
resolve(r)
},
error: function(e) {
const r = {
success: false,
message: '网络错误, 请重新尝试',
}
+ resolve(r)
},
}
if (method === 'post') {
const data = JSON.stringify(form)
request.data = data
}
ajax(request)
})
return p
}
然后在前端把message用弹窗显示出来
//请求列表数据
getDataSourseList = async () => {
const { success, message, data } = await AllService.getList()
+ if(success) {
this.setState({
dataSource:data,
})
+ }else {
+ Message.error(`${message}`)
+ }
}
Message为antd的弹窗组件, 如果只是简单显示可以用alret()
这里分享一个插曲: antd组件的弹窗组件默认的组件名为小写 message
而我这里协议定的字段message同样是小写, 所以变量冲突,
这个时候我有两个选择
- 要么我认怂,改协议
- 要么,和恶势力怼到底
我选择第二个,改了antd源码, 将小写换成了大写 :) 符合我们这次的主题
当然情况还有很多, 到时根据状态码设置返回信息即可
后端捕捉(怼)前端错误
前端在调试过程中,有可能发送非法数据给后端,
后端在不知情的情况下,操作数据库,很有可能会发生各种灵异事件
所以后端需要对前端传递的参数进行校正, 并发出提醒
这样才能更好的和前端合作(撕逼)
具体做法:
- 引入校正函数
//tools/commFunc.js
/**
* 校验 前端请求体
* @param args 前端请求体
* @param field_list 后端modal定义结构体的 keys
* @param require_list 必须字段
* @param is_not_strict 是否严格匹配
*/
exports.assemble_args = function (args, field_list, require_list, is_not_strict) {
let kwargs = {};
let key_list = [];
let err_msg = false;
for (let field_name of field_list) {
let has_key = (args[field_name] !== undefined);
if (!is_not_strict) {
has_key = has_key && (args[field_name] !== '');
}
if (has_key) {
let key = field_name;
let value = args[field_name];
if (typeof value === 'string') {
value = value.trim();
} else if (field_name == 'id') {
value = parseInt(value);
}
key_list.push(field_name);
kwargs[key] = value;
}
}
if (require_list && require_list.length) {
let lack_params = [];
for (let k of require_list) {
if (key_list.indexOf(k) == -1) {
lack_params.push(k);
}
}
if (lack_params.length) {
err_msg = `lack params:${lack_params.toString()}`
//throw err_msg;
}
}
return { kwargs, err_msg };
};
exports.goto_err = function (ret, err_msg) {
ret.success = false;
ret.message = err_msg;
ret.code = 400;
return ret;
};
2.检查请求体中是否有必须字段, 若缺失, 则把缺失信息发送给前端
//routes/all/js
main.post('/update', async (request, response) => {
let ret = {
"success": true,
"code": 200,
"message": "",
"data": [],
}
const body = request.body,
id = body.id || 0,
status = body.status || 0
- const args = body
if (!id) {
//新建
+ //后端定义的keys
+ const field_list = Object.keys(Model.schema.paths).filter(e=> e!='_id' && e!='id')
+ //检查请求体中是否有 name, age, address
+ //若缺失,则把缺失信息发送给前端
+ const { kwargs, err_msg } = commFunc.assemble_args(body, field_list, ['name', 'age', 'address'])
+ if( err_msg ) {
+ let err = commFunc.goto_err(ret, err_msg)
+ response.send(err)
+ return
+ }
const dataSourceObj = await Model.create(kwargs)
ret.data = {
id: dataSourceObj.id, create:true
}
} else if (!status) {
//修改
+ const kwargs = body
const dataSourceObj = await Model.findOne({id: kwargs.id})
+ if(!dataSourceObj) {
+ let err = commFunc.goto_err(ret, `dataSourceInfo id:${kwargs.id} query empty`)
+ response.send(err)
+ return
+ }
for ( let key in kwargs) {
if(key =='_id' || key =='id' ) {
continue
}
dataSourceObj[key]= kwargs[key]
}
const new_dataSourceObj = await dataSourceObj.save()
ret.data = {
id: new_dataSourceObj.id, update:true
}
} else if (status === -1){
//删除
+ const kwargs = body
const dataSourceObj = await Model.findOne({id: kwargs.id})
+ if(!dataSourceObj) {
+ let err = commFunc.goto_err(ret, `dataSourceInfo id:${kwargs.id} query empty`)
+ response.send(err)
+ return
+ }
const remove = await dataSourceObj.remove()
ret.data = {
id: dataSourceObj.id, delete:true
}
}
response.send(ret)
})
测试失败示例:
-
创建时缺少参数
-
修改, 删除时,id不存在
(完...)