现象描述
在开发微信小程序时发现一个很有意思的 Edge Case:
当我在 Page A 调用 wx.navigateTo 跳转到 Page B,并在 Page B 的 onLoad(或 onShow、onReady)生命周期中立刻执行 wx.reLaunch 时,会导致页面彻底白屏。
等待约 10 秒左右,控制台会先后抛出两个 Timeout 错误:
navigateTo:fail timeoutreLaunch:fail timeout

逻辑分析
根据表现来看,这应该是小程序底层路由状态机的一个死锁现象。
- 路由锁冲突:
navigateTo在执行过程中会占用路由锁,直到目标页面生命周期初始执行完成,才算一次完整的跳转切换。 - 重置请求被挂起:在
onLoad中执行reLaunch时,由于前一个MapsTo的任务流尚未完全 Close,reLaunch无法立刻获取路由控制权,进入等待队列。 - 循环等待:
reLaunch的指令理论上应该销毁当前页面栈,但它又在等待navigateTo确认“跳转已完成”;而navigateTo因为页面即将被销毁(由于 reLaunch 的介入),无法正常进入 Ready 状态。
结果就是两个 API 互相卡住,直到触发底层设定的超时阈值。
实验结论
在页面尚未完成 navigateTo 的跳转之前,不要尝试立即进行全局路由重置。
如果业务上确实需要在进入页面后立即根据逻辑(比如权限校验失败)重定向回首页,目前最简单的绕过方案是给 reLaunch 加一个稍长的 setTimeout,这样几乎就不会有问题了:
// Page B.js
onLoad() {
// 确保 navigateTo 的任务流先走完
setTimeout(() => {
wx.reLaunch({
url: '/pages/index/index'
});
}, 500);
}
更优雅的方案
我相信绝大多数程序员,在看到 setTimeout 这种硬等的解决方案的时候都会有一些不适,所以这里也提供一个更优雅的方案。
我们可以尝试对 wx 对象进行一层代理,通过一个全局的 isRouting 标志位来管理路由状态。
- 核心思路:拦截与队列 我们可以重写 wx.navigateTo 和 wx.reLaunch。
当 navigateTo 开始时,设置 isRouting = true。
当跳转完成(success 或 complete 回调触发)时,释放锁。
如果执行 reLaunch 时发现锁未释放,则将任务挂起,等待锁释放后再执行。
- 代码实现
你可以将这段逻辑封装在 app.js 或者其他能在全局优先加载的 js 中:
const originalNavigateTo = wx.navigateTo;
const originalReLaunch = wx.reLaunch;
let isRouting = false; // 路由锁定标志位
// 劫持 navigateTo
wx.navigateTo = function (options) {
isRouting = true;
const { complete } = options;
options.complete = function (...args) {
isRouting = false; // 页面入栈流程结束,释放锁
complete && complete.apply(this, args);
};
return originalNavigateTo.call(this, options);
};
// 劫持 reLaunch
wx.reLaunch = function (options) {
if (isRouting) {
// 如果当前正在路由中,则递归等待锁释放
console.warn('[Router] Detected routing conflict, queuing reLaunch...');
return setTimeout(() => wx.reLaunch(options), 50);
}
return originalReLaunch.call(this, options);
};
