绝大多数程序只考虑了接口正常工作的场景,而用户在使用我们的产品时遇到的各类异常,全都丢在看似 ok 的 try catch 中。如果没有做好异常的兼容和兜底处理,会极大的影响用户体验,严重的还会带来安全和资损风险。

接口异常,通常可以分为以下三类:

  • CGI 逻辑出错。如调用方入参缺失类业务逻辑报错;

  • 服务不稳定。如服务器不稳定导致 nginx 各类 500、502,cgi 路径调整导致的 404

  • 用户网络环境差。如,网络不稳定、网速慢、运营商劫持等

那么,我们在写代码时,如何快速的模拟这些接口异常,做好程序的兼容处理呢?

今天向大家介绍网络调试神器 whistle 的网络异常调试方法,如果你还没用过 whistle,请参考《8102 年的程序员不需要 Hosts 和 Fiddler》。

假设我们有以下前端页面 index.html,放置在自己的本地路径:

<p id="success" style="color:green;"></p>
<p id="fail" style="color:red;"></p>
<script>
fetch(`/mock?r=${Math.random()}`)
.then(response => {
return response.json()
})
.then(v => {
document.getElementById('success').innerHTML = v.data;
}).catch(err => {
document.getElementById('fail').innerHTML = err.message;
})
</script>

接下来,打开 whistle Rules 配置面板 http://127.0.0.1:8899/#rules ,配置模拟的 demo page 和 mock CGI:

*/mock file://({"code":0,"data":"success"}) # 配置 mock cgi 为模拟的 json 数据
example.com file:///Users/kaiye/Projects/Markdown/20181213/ # 配置任意域名到本地 demo 目录,这里注意替换成自己的路径

打开 http://example.com ,正常逻辑下页面展示出了绿色的 success ,现在我们开始加入一些网络异常。

1、业务逻辑异常处理

例如 CGI 没有返回 data 字段,而是返回了一个错误码 code 和对应的 message,针对这种业务逻辑异常我们只需在第二个 then 中做好 code 值的判断即可(注意,这里的 code、message、data 只是示例,实际业务 CGI 中的 JSON 结构体的字段名很可能不同):

fetch(`/mock?r=${Math.random()}`)
.then(response => response.json())
.then((v) => {
// 业务逻辑异常处理
if (v.code !== 0) {
return Promise.reject(new Error(`ERROR_LOGIC_CODE:${v.code}`));
}
document.getElementById('success').innerHTML = v.data;
})
.catch((err) => {
document.getElementById('fail').innerHTML = err.message;
});

相应的 whistle 配置如下:

*/mock file://({"code":12345,"message":"some_logic_error"}) # 模拟业务逻辑异常

2、服务器异常处理

如果服务器直接抛出了 502 错误码,我们希望代码能给用户提示的同时,再做一个异常上报。

fetch(`/mock?r=${Math.random()}`)
.then((response) => {
// 服务器异常处理
if (response.ok) {
return response.json();
}
return Promise.reject(new Error(`ERROR_STATUS_CODE:${response.status}`));
})
.then((v) => {
// 业务逻辑异常处理
if (v.code !== 0) {
return Promise.reject(new Error(`ERROR_LOGIC_CODE:${v.code}`));
}
document.getElementById('success').innerHTML = v.data;
})
.catch((err) => {
const [type, value] = err.message.split(':');
// 异常类型上报
console.log(type, value);
document.getElementById('fail').innerHTML = err.message;
});
 

通过 whistle 的模拟配置如下:

*/mock statusCode://502 # 模拟 HTTP 状态码异常

3、接口被劫持注入

如果 CGI 被运营商劫持注入,可能导致接口返回一个不合法的 JSON 结构,最前面的 response.json() 会抛异常,我们可以提前 catch 住:

fetch(`/mock?r=${Math.random()}`).then((response) => {
// 服务器异常处理
if (response.ok) {
return (
response
.json()
// 接口数据解码异常处理
.catch(err => Promise.reject(new Error('ERROR_DECODE_JSON')))
);
}
return Promise.reject(new Error(`ERROR_STATUS_CODE:${response.status}`));
});

whistle 模拟配置如下:

*/mock file://(<div>hijacking</div>{"code":0,"data":"success"}) # 模拟接口被劫持注入 1

借助 htmlAppendvalues 配置,可以模拟更复杂的注入示例:

*/mock file://({"code":0,"data":"success"}) htmlAppend://{hijacking.html} # 模拟接口被劫持注入 2
```hijacking.html
<script>
alert('hijacking')
</script>
```

4、用户网络不稳定

如果我们要模拟请求发出 10 秒后断网或网络不通的情况,可以通过 whistle 这样配置:

*/mock reqDelay://10000 enable://abort # 模拟 10 秒超时后网络不通

让用户苦苦等待 10 秒,再报错的体验太糟糕。我们可以封装一个能配置超时时间的请求发送函数,同时把上面提到的错误异常都一起配置进来。

<p id="success" style="color:green;"></p>
<p id="fail" style="color:red;"></p>
<script>
function myFetch(url, configOptions) {
const options = Object.assign(
{
timeout: 3000
},
configOptions
)
const { timeout } = options
return new Promise((resolve, reject) => {
// 超时异常处理
const timer = setTimeout(() => {
reject(new Error(`ERROR_TIMEOUT:${timeout}`))
}, timeout)
fetch(url, options)
.then(data => {
clearTimeout(timer)
resolve(data)
})
.catch(err => {
clearTimeout(timer)
reject(err)
})
})
.then(response => {
// 服务器异常处理
if (response.ok) {
return (
response
.json()
// 接口数据解码异常处理
.catch(err => Promise.reject(new Error('ERROR_DECODE_JSON')))
)
} else {
return Promise.reject(
new Error(`ERROR_STATUS_CODE:${response.status}`)
)
}
})
.then(v => {
// 业务逻辑异常处理
if (v.code !== 0) {
return Promise.reject(new Error(`ERROR_LOGIC_CODE:${v.code}`))
} else {
return v.data
}
})
.catch(err => {
const [type, value] = err.message.split(':')
// 异常类型上报
console.log(type, value)
return Promise.reject(err)
})
}
myFetch(`/mock?r=${Math.random()}`)
.then(data => {
document.getElementById('success').innerHTML = data
})
.catch(err => {
document.getElementById('fail').innerHTML = err.message
})
</script>

这样,自定义的 myFetch 只需关注业务具体逻辑,针对不同的 catch error 做对应的处理。

除以上提到的协议命令字外,whistle 还支持 resSpeed 用于模拟低网速传输(单位:kb/s),tpl 协议则可以根据请求传入参数来动态模拟不同的数据在 Frames 面板,还可以对 WebSocket/Socket 请求进行暂停、延迟等网络异常的模拟。

小程序 fetch API 实现

最后,留一道思考题。

近来微信小程序开发非常火,小程序原生提供的 wx.request API 能用于发送 HTTPS 请求,请在它的基础之上进行封装,支持 promise 调用和 timeout 超时时间定义(小程序默认的请求超时定义在 app.json 中,不够灵活),并针对以上提到的 HTTP 状态码异常、接口劫持注入、慢网络、无网络状态等各种网络异常进行兼容处理。

欢迎留言分享你的代码实现,在公众号「猫哥学前班」中回复关键词 request ,可以参考我的实现和 whistle rules 配置。

最新文章

  1. 封装 用canvas绘制直线的函数--面向对象
  2. SharePoint 2013 图文开发系列之事件接收器
  3. 重写TextField Rect 改变显示位置
  4. Spring和mybatis整合后的mybais-config.xml配置
  5. js算法之最常用的排序
  6. 代码快捷键的设置读取App.config方法
  7. Android String format 通过value 下的string.xml 文件
  8. mysql定时器三部曲
  9. Js冒泡事件和捕获事件
  10. jquery 绑定动态元素
  11. 【Java线程】Lock、Condition
  12. T-SQL基础(2) - 单表查询
  13. postgreSQL-如何查数据库表、字段以及字段类型、注释等信息?
  14. java之过滤器Filter (应用场景)
  15. IOS设备设计完整指南
  16. .NET CORE学习笔记系列(5)——ASP.NET CORE的运行原理解析
  17. animate和translate
  18. multi role
  19. 微信小程序-flex布局中align-items和align-self区别
  20. [多问几个为什么]为什么匿名内部类中引用的局部变量和参数需要final而成员字段不用?(转)

热门文章

  1. WPF MVVM UI分离之《交互与数据分离》
  2. if_else_while_for
  3. BZOJ_3613_[Heoi2014]南园满地堆轻絮_二分答案
  4. 映射内网ftp服务器到公网后内网访问出错问题
  5. Centos 7 Linux系统修改网卡名称为ethx
  6. object类和内部类
  7. 【TensorFlow基础】tf.add 和 tf.nn.bias_add 的区别
  8. asp.net core系列 43 Web应用 Session分布式存储(in memory与Redis)
  9. HTTP/2 简介
  10. 面试前必须要知道的Redis面试题