API接口超时,网络波动,不要一直弹Alert了!

341 阅读4分钟

前言

前段时间,我司的IoT平台的客户大量上新了一堆设备后,在使用过程中,出现了网络连接超时服务器错误等问题,客户小题大做,抓着这个尾巴不放手,喷我司系统不健壮,要求我们杜绝API错误

由于IoT多是日志、设备、测试、Action、图表、通信类的功能,API请求&返回数据量大、时间长。在后端添加索引的情况下,接口仍然达不到秒级,接口普遍都是1~3s,部分接口甚至达到小10s。我司把服务器的硬盘和CPU都进行了一波升级,效果都不是很理想。

这个烫手的山芋,落在了前端的头上。我们被迫进行了一波系统升级优化

解决方案

我们结合这个需求,制定了以下几条标准:

  1. 不能入侵其他的功能
  2. 对系统的破坏尽可能的小
  3. 杜绝或者尽可能的减少弹框问题
  4. 保证数据的正确展示,对于错误要正确的暴露出来

根据以上几条标准,于是方案就自然的确定了:

API请求时间

拉长API的请求时间,将超时时间由30s,更新为60s

const service = axios.create({
  baseURL: '/xxx',
  timeout: 60 * 1000 // 请求超时时间
})

重发机制

  1. API请求超时: 当新页面大量接口请求,某个接口请求时间过长,总时间>60s时,我们会对这个接口进行至多重发3次,用180s的时间去处理这个接口,当请求成功后,关闭请求

重发的接口不能超过timeout时长,否则一直会重发失败。也可以自定义重发的时间

  1. 偶发的服务器异常: 当接口出现50X时,重发一次

可以使用axois自带的方法,也可以使用axios-retry插件,axios-retry插件更简单,底层也是axois方法实现的。相比较而言,axios-retry更简单,但不知为什么,我没有实现

// request.js
// 默认重发3次,每次的间隔时间为3s
service.defaults.retry = 3;
service.defaults.retryDelay = 3000;

export function againRequest(
  error,
  axios,
  time = error.config.retry
) {
  const config = error.config;

  if (!config || !config.retry) return Promise.reject(error);

  // 设置用于记录重试计数的变量 默认为0
  config.__retryCount = config.__retryCount || 0;

  // 判断是否超过了重试次数
  if (config.__retryCount >= time) {
    // alert

    return Promise.reject(error);
  }

  config.__retryCount += 1;

  const backoff = new Promise(resolve => {
    setTimeout(() => {
      resolve();
    }, config.retryDelay || 1);
  });

  return backoff.then(() => {
    /*
       以下三行,根据项目实际调整
       这三行是处理重发请求接口变成string,并且重定向的问题
    */
    if (config.data && isJsonStr(config.data)) {
      config.data = JSON.parse(config.data);
    }
    config.baseURL = "/";
    
    return axios(config);
  });
}

export let isLoop = config => {
  if (
    config.isLoop &&
    config.isLoop.count >= 0 &&
    config.url == config.isLoop.url
  ) {
    return true;
  }
  return false;
};

export let isJsonStr = str => {
  if (typeof str == "string") {
    try {
      var obj = JSON.parse(str);
      if (typeof obj == "object" && obj) {
        return true;
      } else {
        return false;
      }
    } catch (e) {
      console.log("error: " + str + "!!!" + e);
      return false;
    }
  }
};

注意到是: axois不能是0.19.x

issue中提到使用0.18.0。经过实践,0.20.x也是可以的,但需要进行不同的配置,具体请看issue。参考资料中有提到 axios issues github

也可以使用axios-retry

axios-retry

npm install axios-retry

// ES6
import axiosRetry from 'axios-retry';

axiosRetry(axios, { retries: 3 });

取消机制

当路由发生变化时,取消上一个路由正在请求的API接口

监控路由页面: 调用cancelAllRequest方法

// request.js
const pendingRequests = new Set();

service.cancelAllRequest = () => {
  pendingRequests.forEach(cancel => cancel());
  pendingRequests.clear();
};

轮询

轮询有2种情况,一种是定时器不停的请求,一种是监听请求N次后停止。

比如: 监听高低电平的变化 - 如快递柜的打开&关闭。

  1. 一直轮询的请求:

    • 使用WebSocket
    • 连续失败N次后,谈框。<N次的请求中,请求成功了,则重置计数器
  2. 轮询N次的请求:

    • 连续失败N次后,谈框。<N次的请求中,请求成功了,则重置计数器
export function api(data, retryCount) {
  return request({
    url: `/xxx`,
    method: "post",
    isLoop: {
      url: "/xxx",
      count: retryCount
    },
    data: { body: { ...data } }
  });
}

自定义api url的原因是:

同一个页面中,有正常的接口和轮询的接口,url是区分是否当前的接口是否是轮询的接口

监听滚动

对于图表类的功能,监听滚动事件,根据不同的高度请求对应的API

节流机制

  1. 用户连续多次请求同一个API
    • 按钮loading。最简单有效
    • 保留最新的API请求,取消相同的请求

错误码解析

网络错误 & 断网

if (error.toString().indexOf("Network Error") !== -1) {
        if (configData.isLoop && configData.isLoop.count >= 0) {
          networkTimeout();
          // 关闭所有的定时器
          let t = setInterval(function() {}, 100);
          for (let i = 1; i <= t; i++) {
            clearInterval(i);
          }
          return;
        }
        networkTimeout();
      }

404

else if (error.toString().indexOf("404") !== -1) {
    // 404
}

401

else if (error.toString().indexOf("401") !== -1) {
    // 清除token,及相应的数据,返回到登录页面
}

超时

else if (error.toString().indexOf("Error: timeout") !== -1) {
        if (!axios.isCancel(error) && !isLoop(configData)) {
          return againRequest(error, service);
        }
        if (configData.isLoop && configData.isLoop.count === 3) {
          requestTimeout();
        }
      }

50X

else if (error.toString().indexOf("50") !== -1) {
    if (!axios.isCancel(error) && !isLoop(configData)) {
      return againRequest(error, service, 1, 2);
    }
    if (configData.isLoop && configData.isLoop.count === 1) {
      requestTimeout();
    }
  } 

未知错误

else {
      // 未知错误,等待以后解析
    }

总结

结果将状态码梳理后,客户基本看不到API错误了,对服务的稳定性和可靠性非常满意,给我们提出了表扬和感谢。我们也期待老板升职加薪!

参考资料

  1. axios issues github
  2. axios-retry
  3. axios请求终极封装
  4. axios