最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 前端如何友好的处理xhr失败

    正文概述 掘金(Ego_Tao)   2021-02-24   1080

    前端如何友好的处理xhr失败

    需求

    1. 请求失败(包括http和业务逻辑上的失败),做出友好合理的提示
    2. 对于登录态失效等特殊情况,避免多个请求同时失败,重复提示
    3. 避免重复写提示部分代码
    4. 有公共的异常处理,也可以在业务代码中处理异常的业务场景

    方案

    规范接口响应体

    最重要的一步,需要与后端定义好response data的内容以及API规范

    // response
    {
      data:{
        data:any; // 实际的数据,可以是数组、对象、字符串等等
        code:number;  // 请求状态码,用于判断接口是否成功(这里的成功是只业务层面的,不是http层面)
        message:string; // 消息,用于前端展示(主要是异常场景)
      },
      // 其他属性
    }
    

    统一提示

    解决重复写提示信息部分代码很好解决,直接在请求的拦截器里处理就好。因为使用了axios,所以就拿axios来做例子。

    import axios from 'axios';
    // axios实例
    export const http = axios.create({
      baseURL: '/',
      timeout: 5000,
      withCredentials: true,
      validateStatus: (status) => status >= 200 && status < 400, // http状态码的拦截,根据接口规则随机应变
    });
    // response拦截器
    http.interceptors.response.use((res) => {
      // 与后端商定,接口code不为200均为不正常
      if (res.data.code !== 200) {
        // 公共异常业务处理,如登录态失效
        // 登录态失效code码为401
        if (res.data.code === 401) {
          // 清除缓存、数据等
          initData();
          history.push('/login'); // 进入登录页或者首页之类的
          message.error('登录失效,请重新登录'); // 提示用户
          throw new Error('LOGIN_FAILED');  // 抛出错误码
        }
        // 提示异常信息
        message.error(res.data.message);
        // 抛出错误码,业务代码中可以根据catch到的code做业务处理
        throw new Error(res.data.code);
      }
      // 这边可以使用 return res.data; 因为大多数场景下业务中只需要使用res.data
      // 这样业务代码就只用res.data就可以获取数据,不需要res.data.data;
      return res;
    }, (err) => {
      // 处理axios的拦截器,主要就是上面状态码的拦截
      // 定义一些常见错误码,比如Network Error或者timeout之类的
      if (err.message === 'Network Error') {
        message.error('网络异常,请检查后重试');
      } else {
        err.message && message.error(err.message);
      }
      throw new Error(err);
    });
    

    ErrorMessage处理

    上面代码中是直接弹出res.data.message,也就是后端给什么前端就提示什么。这样的就会带来另外的问题:多语言的处理;不同项目调用同一个服务提示的文案不同的场景等待。 为了解决这个问题,前端可以在每个项目里定义一个MessageMap,用来映射后端抛出的message。这样就可以通过i18n之类的库轻松做到多语言,也可以满足不同项目中对同一message提示不同内容。为了避免在多个项目中重复写MessageMap,可以考虑使用公共的远程文件/npm库来提供公共内容,在通过自定义内容覆盖合并满足自定义。

    取消请求

    进入页面时同时发起多个异步请求是很常见的场景。如果这个时候用户的登录态失效了,这样会发起的多个请求,实际上都会失败。会造成资源浪费(给服务器发出了不必要的请求),前端也会做出多个提示(当然这个可以通过其他方案解决)。这都是我们不希望看到的,解决这个问题有2个方案:第一就是提供一个同步接口,接口成功了才继续渲染;第二个就是当有一个请求失败了,就取消其他请求。这里我选择第二个方案,第一个方案容易引起首屏渲染或者等待交互时间过长。 直接上代码:

    // axios提供了一个cancel token用于取消某一个请求
    // 定义一个cancel token
    let source = axios.CancelToken.source();
    // request拦截器
    http.interceptors.request.use((config) => {
      // 为当前请求设置cancel Token,后面会讲为什么不直接在创建axios实例的时候设置
      config.cancelToken = source.token;
      return config;
    }, (err) => err);
    http.interceptors.response.use((res) => {
      if (res.data.code !== 200) {
        if (res.data.code === 401) {
          // 登录失效了 取消其他请求
          source.cancel();
          // 重置token
          source = axios.CancelToken.source();
          message.error('登录失效,请重新登录');
          throw new Error('登录失效,请重新登录');
        } else {
          message.error(res.data.message);
          throw new Error(res.data.message);
        }
      }
      return res;
    }, (err) => {
      // handle error
    });
    

    axios创建实例的时候可以直接设置Cancel Token,没有这么做是因为当执行了source.cancel()后,所有是这个token的请求都会被取消,无论发起时间。也就是说,如果不改变cancel token,在执行source.cancel()后,所有请求都无法发出,直接被取消。通过在发起请求时设置一次当前的token,可以避免这个问题。 上面代码还有个问题,就是如果需要针对某一个请求进行取消的时候,因为在request拦截器里做了处理,所以需要进行一些处理。

    http.interceptors.request.use((config) => {
      // 没有才设置,有的话就用原本的
      if (!config.cancelToken) {
        config.cancelToken = source.token;
      }
      return config;
    }, (err) => err);
    

    总结

    这样已经满足了开头所述的需求,这么做的前提是和后端规范好API规范,例如使用RESTful或者怎么样。否则,前端只能针对不同接口进行不同处理。最后贴个完整代码: http.js

    // 申明axios实例
    import axios from 'axios';
    import message from 'utils/message';  // toast方法
    import { errorMessage } from './error-message';
    // axios实例
    export const http = axios.create({
      baseURL: '/',
      timeout: 5000,
      withCredentials: true,
      validateStatus: (status) => status >= 200 && status < 400, // http状态码的拦截,根据接口规则随机应变
    });
    // 申明cancelToken
    let source = axios.CancelToken.source();
    // request拦截器
    http.interceptors.request.use((config) => {
      // 为当前请求设置cancel Token
      config.cancelToken = source.token;
      return config;
    }, (err) => err); // 这边的error也可以做处理
    // response拦截器
    http.interceptors.response.use((res) => {
      // 与后端商定,接口code不为200均为不正常
      if (res.data.code !== 200) {
        // 公共异常业务处理,如登录态失效
        // 登录态失效code码为401
        if (res.data.code === 401) {
          // 登录失效了 取消其他请求
          source.cancel();
          // 重置token
          source = axios.CancelToken.source();
          // 清除缓存、数据等
          initData();
          history.push('/login'); // 进入登录页或者首页之类的
          message.error('登录失效,请重新登录'); // 提示用户
          throw new Error('LOGIN_FAILED');  // 抛出错误码
        } else {
          // 提示异常信息
          message.error(errorMessage(res.data.message));
          // 抛出错误码,业务代码中可以根据catch到的code做业务处理
          throw new Error(res.data.code);
        }
        // 还可以根据需要进行打点
        log({
          msg: 'api error',
          path: 'query path',
          // 需要的数据
        });
      }
      // 这边可以使用 return res.data; 因为大多数场景下业务中只需要使用res.data
      // 这样业务代码就只用res.data就可以获取数据,不需要res.data.data;
      return res;
    }, (err) => {
      // 处理axios的拦截器,主要就是上面状态码的拦截
      // 定义一些常见错误码,比如Network Error或者timeout之类的
      if (err.message === 'Network Error') {
        message.error('网络异常,请检查后重试');
      } else {
        err.message && message.error(err.message);
      }
      throw new Error(err);
    });
    

    error-message.js

    export const errorMessage = (msg:string) => {
      // key由后端定义 value由前端定义
      const messageMap = {
        'message1': '请求失败,请重试',
        'message2': '请求失败,请重试',
        'message3': '请求失败,请重试',
      };
      return messageMap[msg] || '未知错误,请重试';
    }
    

    业务代码

    const queryData = async () => {
      try {
        const res = await fetchData();
        // 业务逻辑
      } catch (err) {
        // 异常处理
        if (err.message === 'xxxx') {
          // 处理
        }
        // 业务打点
        log('xxx error', err);
      }
    }
    

    起源地下载网 » 前端如何友好的处理xhr失败

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元