前言
今天看到同事在CSDN上写技术文档挣外快,再想想自己那给程序员同行“丢脸”的工资,我决定……也去抢抢饭碗。 不过,考虑到我一出手,同事可能就没饭碗了,于是我决定换个平台——我就来掘金“污染”读者老爷们的眼睛辣。
刚想在心里把程序员生涯的所学梳理一遍,一气呵成完成处女作,却突然意识到一个残酷的事实:像Java、Spring Cloud基础这种知识,我自己会付费看吗?
当然不会!我甚至都懒得搜,ChatGPT不香吗?白嫖一年的Gemini会员不好用吗?
于是,我决定写点“稀缺货”,绞尽脑汁,最终憋出了这篇文章。
问题
言归正传。我遇到的问题是:我在给停车场做一个停车缴费的微信小程序(uni-app实现),突然有一天,“甲方爸爸”发来消息,说他在停车场出口扫码缴费时,小程序突然一片空白,啥内容都没有。
但之前明明是正常的,一点问题没有。
于是我赶紧查了微信日志(实际上,我是经历了一系列疑惑、迷茫、怀疑网络、怀疑后台、怀疑甲方手机、怀疑人生的折腾后,才想起来自己埋了微信日志的):
思考
梳理一下手上的信息:
- 这个接口是通过微信 unionId 到 Java 后台获取用户信息的;
- 这个接口是写在 uni-app 的
onLaunch生命周期函数内的;
所以问题很简单嘛:获取用户信息的接口失败了呗!问题解决……个鬼啊。
为啥会失败呢?我的眼神从茫然,到“柯南洞察一切”,又迅速变回了“清澈的愚蠢”。
仔细分析了微信的报错日志:fail canceled。
这说明 wx.request 请求发出去了,但是被主动取消了。不是404,也不是500,大概率和后台没关系。关键就在“主动取消”这个动作。
我问了亲爱的ChatGPT,仔细“甄别”了它的胡言乱语后,静下心来大胆猜测:会不会是uni-app框架本身的问题,导致框架主动中断了请求?
没问题,你的原文风格(幽默、自嘲)非常鲜明,很适合技术社区的氛围。我帮你做了一些优化,主要集中在措辞的精炼、技术术语的准确性(如“同步”改为“并行”)以及语句的流畅度上,让读者在get到你的幽默的同时,也能更清晰地理解技术点。
这是优化后的版本,保留了你所有的幽默元素:
前言
今天看到同事在CSDN上写技术文档挣外快,再想想自己那给程序员同行“丢脸”的工资,我决定……也去抢抢饭碗。 不过,考虑到我一出手,同事可能就没饭碗了,出于(对自己的实力)怜悯(同事),我决定换个平台。
于是,我就来掘金“污染”读者老爷们的眼睛了。
刚想在心里把程序员生涯的所学梳理一遍,一气呵成完成处女作,却突然意识到一个残酷的事实:像Java、Spring Cloud基础这种知识,我自己会付费看吗?
当然不会!我甚至都懒得搜,ChatGPT不香吗?白嫖一年的Gemini会员不好用吗?
于是,我决定写点“稀缺货”,绞尽脑汁,最终憋出了这篇文章。
问题
言归正传。我遇到的问题是:我在给停车场做一个停车缴费的微信小程序(uni-app实现),突然有一天,“甲方爸爸”发来消息,说他在停车场出口扫码缴费时,小程序突然一片空白,啥内容都没有。
但之前明明是正常的,一点问题没有。
于是我赶紧查了微信日志(实际上,我是经历了一系列疑惑、迷茫、怀疑网络、怀疑后台、怀疑甲方手机、怀疑人生……的折腾后,才想起来自己埋了微信日志的):
思考
梳理一下手上的信息:
- 这个接口是通过微信 unionId 到 Java 后台获取用户信息的;
- 这个接口是写在 uni-app 的
onLaunch生命周期函数内的;
所以问题很简单嘛:获取用户信息的接口失败了呗!问题解决……个鬼啊。
为啥会失败呢?我的眼神从茫然,到“柯南洞察一切”,又迅速变回了“清澈的愚蠢”。
仔细分析了微信的报错日志:fail canceled。
这说明 wx.request 请求发出去了,但是被主动取消了。不是404,也不是500,大概率和后台没关系。关键就在“主动取消”这个动作。
我问了亲爱的ChatGPT,仔细“甄别”了它的胡言乱语后,静下心来大胆猜测:会不会是uni-app框架本身的问题,导致框架主动中断了请求?
跟着这个思路,继续压榨ChatGPT,确认了一点:onLaunch 里的异步请求,并不会“阻塞”后续页面的 onMount 和 onLoad。它们很可能会并行执行(原文的“同步执行”在这里容易引起误解,“并行”或“并发”更准确)。
那么,如果手机性能不足,框架确实有可能为了确保高优先级任务(比如页面渲染)的执行,而主动中断一些它认为优先级不高的请求(比如 onLaunch 里的网络请求)。
到这儿,我觉得八九不离十了。本着“先射箭再画靶”的优秀思想,我开始改造代码:在所有页面的生命周期函数(如 onLoad)里,都等待 onLaunch 里的Promise执行完成后再继续执行。
修改并上线后,我开始观察微信日志,在可见的时间内(直到发文),这个报错再也没出现过。基本可以确定,问题就出在这里。
到这儿,问题基本就算解决了。但我还有两个疑惑:
- 为什么这个问题是偶发的?
- 在每个页面的生命周期函数里都去等待
onLaunch执行完成,这种写法太蠢了,怎么优化?
优化
对于这两个问题,我逐一进行了分析和尝试:
1. 偶发性
我找了组内其他小伙伴,用他们的手机一一尝试,都没能复现。这说明问题确实是偶发的,也导致了它在开发测试时未暴露,最终“惊艳”了客户。
(咱们小公司,没有那么系统的测试,也不会对不同机型专门测,我想这种情况才是行业内常态吧)。
通过控制变量排查,最后我们得出了一个结论:之前怀疑“甲方爸爸”手机的玩笑,竟然是真的。就是他的手机性能限制,导致了这个问题。而我们组内的小伙伴,都是年轻小伙,手机杠杠的,不存在性能瓶"颈",自然不会复现(这说明了大龄程序员存在的必要性(●'◡'●))。
经过几次测试后,情况如下:
- 性能好的手机:一次都没有发生此问题;
- 性能贼差的手机:几乎必现;
- 性能中等的手机:存在很低的概率会出现此问题;(“甲方爸爸”就属于这种情况)
2. 代码优化
在每个页面都写一遍等待逻辑,确实太蠢了。
这里提供一个思路,就不贴具体代码了:借鉴后端 Spring AOP 的思路,增加一个前端全局拦截器。
在执行所有页面的生命周期函数(如 onMounted 或 onLoad)前,先检查 onLaunch 里的逻辑是否执行完毕。确保执行完毕后,才“放行”页面的生命周期函数。用这种方式,还可以方便地给 onLaunch 增加重试机制。
当然,这个实现强行将并行改为了串行,会导致页面加载变慢,但实际测试下来,增加的耗时相对有限(好的机型大概几十到几百毫秒),完全可以接受。
结语
这个BUG,如果在大厂,大概率是可以避免的(别问我为啥知道,呜呜呜……),只需要通过不同机型测试一下,就能轻易地复现。
然而到了小厂,没这个条件。像这种BUG,除非踩过坑或者知识储备特别丰富,否则很难提前规避,确实无奈。
这里把我踩的坑分享出来,给读者老爷们过目,希望大家(特别是做uni-app的)不用再踩一遍。