问题背景
公司的项目大范围的使用fetch api,因为fetch写起来太随意太舒服了,张手就来,于是非常习惯地写出如下的代码:
请求静态资源:
fetch('非常棒的.mp3').then(res=>res.arrayBuffer()).then(ab=>{ xxx}).catch(err=>{alert('语音播放失败')})
其实日常使用这也不存在什么问题,哪一步出错了自然会走到catch,对功能不会有太大影响。但问题来了,当用户看到失败提示了,截个图来问你,你怎么知道这个错误是什么原因引起的,你能区分是资源解析错误了还是资源不存在吗,或者是服务挂了?不,你不能,几乎没法追踪
说来也苦,公司也正好对技术也算起了kpi,每周都会有埋点质量报告,那既然都要对这些错误列入了kpi考核,那只能埋上必要的点来区分错误类型了,要不锅太大背不动
自我救赎:
fetch必要知识点,摘自MDN:
当接收到一个代表错误的 HTTP 状态码时,从
fetch()返回的 Promise 不会被标记为 reject, 即使响应的 HTTP 状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve 的返回值的ok属性设置为 false ),仅当网络故障时或请求被阻止时,才会标记为 reject
所以问题来了,fetch请求只要不是跨域被阻止或者是网络故障,那么结果都是resolve而不是reject,也就是只要不是这两种情况,都会走入then,也就是说如果请求状态码是404,504,502,500等等值一律还是会进入then,所以一不小心就吃了没文化的亏了,试想一下当服务502时前端一直在弹出报错,用户问题解决群里疯狂圈你时你那愤怒的嘴脸,所以还是需要对自己好一点,把能埋的点给埋上,到时候扯皮扯半天。
所以决定在fetch里加个状态检查,只要状态码 >= 200 且 < 300时才算是ok的请求要不就趁早抛出错误,抛出的错误把返回的关键信息也抛出去
const checkPureFetchStatus = res => {
if (res.status >= 200 && res.status < 300) {
return res;
}
const error = new Error(res.status + res.statusText);
const obj = {
httpCode: res.status, //http状态码
statusText: res.statusText,
url: res.url
};
Object.assign(error, obj);
return Promise.reject(error);
};
但埋点要提取error信息,还要取什么message,stack啥的太麻烦了能不能把error信息直接解构呢,于是我在埋点方法里这么写
sensors.track('playError',{
...errror
});
啊,行不通,没法解构。于是找到了将error信息转化成普通对象的方法
JSON.parse(JSON.stringify(error, Object.getOwnPropertyNames(error), 2));
于是最后我写成了这样
const checkPureFetchStatus = res => {
if (res.status >= 200 && res.status < 300) {
return res;
}
const error = new Error(res.status + res.statusText);
const obj = {
httpCode: res.status, //http状态码
statusText: res.statusText,
url: res.url
};
Object.assign(error, obj);
return Promise.reject(error);
};
const parseErrorToPlainObj = error => {
const objToString = function (o){
return Object.prototype.toString.call(o);
};
const fn = function (){
if (typeof error !== 'object' || error === null) {
return {};
}
if (objToString(error) === '[object Error]') {
return JSON.parse(JSON.stringify(error, Object.getOwnPropertyNames(error), 2));
//将error信息转化可序列化的json
}
return error;
};
return fn();
};
fetch('神秘的url.mp3').then(res=>checkPureFetchStatus).then(res=>res.arrayBuffer()).then(ab=>{
//神一样的代码balabala
....
}).catch(err=>{
sensors.track('fetchError',{
...parseErrorToPlainObj(err)
});
})