fetch请求你可能容易忽略的小知识点

1,028 阅读3分钟

问题背景

公司的项目大范围的使用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)
  });
})