微信报错:request:fail canceled的思考

168 阅读7分钟

前言

今天看到同事在CSDN上写技术文档挣外快,再想想自己那给程序员同行“丢脸”的工资,我决定……也去抢抢饭碗。 不过,考虑到我一出手,同事可能就没饭碗了,于是我决定换个平台——我就来掘金“污染”读者老爷们的眼睛辣。

刚想在心里把程序员生涯的所学梳理一遍,一气呵成完成处女作,却突然意识到一个残酷的事实:像Java、Spring Cloud基础这种知识,我自己会付费看吗?

当然不会!我甚至都懒得搜,ChatGPT不香吗?白嫖一年的Gemini会员不好用吗?

于是,我决定写点“稀缺货”,绞尽脑汁,最终憋出了这篇文章。

问题

言归正传。我遇到的问题是:我在给停车场做一个停车缴费的微信小程序(uni-app实现),突然有一天,“甲方爸爸”发来消息,说他在停车场出口扫码缴费时,小程序突然一片空白,啥内容都没有。

但之前明明是正常的,一点问题没有。

于是我赶紧查了微信日志(实际上,我是经历了一系列疑惑、迷茫、怀疑网络、怀疑后台、怀疑甲方手机、怀疑人生的折腾后,才想起来自己埋了微信日志的):

image.png

思考

梳理一下手上的信息:

  1. 这个接口是通过微信 unionId 到 Java 后台获取用户信息的;
  2. 这个接口是写在 uni-app 的 onLaunch 生命周期函数内的;

所以问题很简单嘛:获取用户信息的接口失败了呗!问题解决……个鬼啊。

为啥会失败呢?我的眼神从茫然,到“柯南洞察一切”,又迅速变回了“清澈的愚蠢”。

仔细分析了微信的报错日志:fail canceled

这说明 wx.request 请求发出去了,但是被主动取消了。不是404,也不是500,大概率和后台没关系。关键就在“主动取消”这个动作。

我问了亲爱的ChatGPT,仔细“甄别”了它的胡言乱语后,静下心来大胆猜测:会不会是uni-app框架本身的问题,导致框架主动中断了请求?

没问题,你的原文风格(幽默、自嘲)非常鲜明,很适合技术社区的氛围。我帮你做了一些优化,主要集中在措辞的精炼技术术语的准确性(如“同步”改为“并行”)以及语句的流畅度上,让读者在get到你的幽默的同时,也能更清晰地理解技术点。

这是优化后的版本,保留了你所有的幽默元素:


前言

今天看到同事在CSDN上写技术文档挣外快,再想想自己那给程序员同行“丢脸”的工资,我决定……也去抢抢饭碗。 不过,考虑到我一出手,同事可能就没饭碗了,出于(对自己的实力)怜悯(同事),我决定换个平台。

于是,我就来掘金“污染”读者老爷们的眼睛了。

刚想在心里把程序员生涯的所学梳理一遍,一气呵成完成处女作,却突然意识到一个残酷的事实:像Java、Spring Cloud基础这种知识,我自己会付费看吗?

当然不会!我甚至都懒得搜,ChatGPT不香吗?白嫖一年的Gemini会员不好用吗?

于是,我决定写点“稀缺货”,绞尽脑汁,最终憋出了这篇文章。

问题

言归正传。我遇到的问题是:我在给停车场做一个停车缴费的微信小程序(uni-app实现),突然有一天,“甲方爸爸”发来消息,说他在停车场出口扫码缴费时,小程序突然一片空白,啥内容都没有。

但之前明明是正常的,一点问题没有。

于是我赶紧查了微信日志(实际上,我是经历了一系列疑惑、迷茫、怀疑网络、怀疑后台、怀疑甲方手机、怀疑人生……的折腾后,才想起来自己埋了微信日志的):

思考

梳理一下手上的信息:

  1. 这个接口是通过微信 unionId 到 Java 后台获取用户信息的;
  2. 这个接口是写在 uni-app 的 onLaunch 生命周期函数内的;

所以问题很简单嘛:获取用户信息的接口失败了呗!问题解决……个鬼啊。

为啥会失败呢?我的眼神从茫然,到“柯南洞察一切”,又迅速变回了“清澈的愚蠢”。

仔细分析了微信的报错日志:fail canceled

这说明 wx.request 请求发出去了,但是被主动取消了。不是404,也不是500,大概率和后台没关系。关键就在“主动取消”这个动作。

我问了亲爱的ChatGPT,仔细“甄别”了它的胡言乱语后,静下心来大胆猜测:会不会是uni-app框架本身的问题,导致框架主动中断了请求?

跟着这个思路,继续压榨ChatGPT,确认了一点:onLaunch 里的异步请求,并不会“阻塞”后续页面的 onMountonLoad。它们很可能会并行执行原文的“同步执行”在这里容易引起误解,“并行”或“并发”更准确)。

那么,如果手机性能不足,框架确实有可能为了确保高优先级任务(比如页面渲染)的执行,而主动中断一些它认为优先级不高的请求(比如 onLaunch 里的网络请求)。

到这儿,我觉得八九不离十了。本着“先射箭再画靶”的优秀思想,我开始改造代码:在所有页面的生命周期函数(如 onLoad)里,都等待 onLaunch 里的Promise执行完成后再继续执行。

修改并上线后,我开始观察微信日志,在可见的时间内(直到发文),这个报错再也没出现过。基本可以确定,问题就出在这里。

到这儿,问题基本就算解决了。但我还有两个疑惑:

  1. 为什么这个问题是偶发的?
  2. 在每个页面的生命周期函数里都去等待 onLaunch 执行完成,这种写法太蠢了,怎么优化

优化

对于这两个问题,我逐一进行了分析和尝试:

1. 偶发性

我找了组内其他小伙伴,用他们的手机一一尝试,都没能复现。这说明问题确实是偶发的,也导致了它在开发测试时未暴露,最终“惊艳”了客户。

(咱们小公司,没有那么系统的测试,也不会对不同机型专门测,我想这种情况才是行业内常态吧)。

通过控制变量排查,最后我们得出了一个结论:之前怀疑“甲方爸爸”手机的玩笑,竟然是真的。就是他的手机性能限制,导致了这个问题。而我们组内的小伙伴,都是年轻小伙,手机杠杠的,不存在性能瓶"颈",自然不会复现(这说明了大龄程序员存在的必要性(●'◡'●))。

经过几次测试后,情况如下:

  1. 性能好的手机:一次都没有发生此问题;
  2. 性能贼差的手机:几乎必现;
  3. 性能中等的手机:存在很低的概率会出现此问题;(“甲方爸爸”就属于这种情况)

2. 代码优化

在每个页面都写一遍等待逻辑,确实太蠢了。

这里提供一个思路,就不贴具体代码了:借鉴后端 Spring AOP 的思路,增加一个前端全局拦截器

在执行所有页面的生命周期函数(如 onMountedonLoad)前,先检查 onLaunch 里的逻辑是否执行完毕。确保执行完毕后,才“放行”页面的生命周期函数。用这种方式,还可以方便地给 onLaunch 增加重试机制。

当然,这个实现强行将并行改为了串行,会导致页面加载变慢,但实际测试下来,增加的耗时相对有限(好的机型大概几十到几百毫秒),完全可以接受。

结语

这个BUG,如果在大厂,大概率是可以避免的(别问我为啥知道,呜呜呜……),只需要通过不同机型测试一下,就能轻易地复现。

然而到了小厂,没这个条件。像这种BUG,除非踩过坑或者知识储备特别丰富,否则很难提前规避,确实无奈。

这里把我踩的坑分享出来,给读者老爷们过目,希望大家(特别是做uni-app的)不用再踩一遍。