注册
web

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

前言

前段时间,我司的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次后,谈框。次的请求中,请求成功了,则重置计数器<>
  2. 轮询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错误了,对服务的稳定性和可靠性非常满意,给我们提出了表扬和感谢。我们也期待老板升职加薪!

参考资料


作者:高志小鹏鹏
来源:juejin.cn/post/7413187186131533861

0 个评论

要回复文章请先登录注册