微信小程序页面跳转后 WebSocket 还没重连,消息就已经丢了
某个 IM 聊天页,用户点击"选择订单"跳到订单列表,挑好之后 navigateBack 回来。理论上应该在聊天框发出一张订单卡片,但实际上:什么都没发出,没有任何错误提示,数据也已经丢失。这个 bug 的诡异之处在于,代码里该有的判断都有——只是全部在错误的时机执行了。
现象
用户从聊天页跳走再返回后,订单卡片没有发送。日志里看不到报错,sendOrderCard 方法被调用了,但进入函数第一行就因连接状态检查而返回。整个过程无声无息。
根因
问题出在 onShow 里三件事的执行顺序上:
onShow调用connectWebSocket()——这是异步的,需要走握手、鉴权才能变成connectedonShow同步读取currentPage.selectedOrder,并立即delete掉(消费一次性数据的惯例)sendOrderCard()检查connectionStatus !== 'connected'→ 此时还是 disconnected → 静默返回
数据在第 2 步被删,连接在第 3 步还没就绪,两者错开,订单永久丢失。
坑:onShow 里同步消费了数据,却等不到异步连接就绪
navigateBack 触发 onShow 时,connectWebSocket() 是异步的——它只是发起重连请求,不会阻塞后续代码。紧跟其后读取并删除 selectedOrder,再调用带连接守卫的发送函数,结果必然是:数据已消费,连接未就绪,发送静默失败。
正解
核心思路:在 onShow 里只做"检查 + 存队列",把真正的发送挂到 onAuthSuccess 回调里执行。
Page({
// 实例属性,不放进 data——不需要响应式,不触发重渲染
_pendingOrder: null,
onShow() {
// 1. 按需重连
const status = chatWs.getStatus()
if (!status.isConnected && !status.isConnecting && this.data.imToken) {
this.connectWebSocket()
}
// 2. 读取并立即消费子页面传回的数据
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
if (currentPage.selectedOrder) {
const order = currentPage.selectedOrder
delete currentPage.selectedOrder // 只消费一次
// 3. 连接就绪直接发;否则入队等待
if (this.data.connectionStatus === 'connected') {
this.sendOrderCard(order)
} else {
this._pendingOrder = order
}
}
},
onAuthSuccess(data) {
// 注意:用 setData 的回调,确保状态写入完成后再读队列
this.setData({ connectionStatus: 'connected' }, () => {
if (this._pendingOrder) {
const order = this._pendingOrder
this._pendingOrder = null
this.sendOrderCard(order)
}
})
},
onAuthFailed() {
this.setData({ connectionStatus: 'failed' })
if (this._pendingOrder) {
this._pendingOrder = null
wx.showToast({ title: '连接失败,请重新操作', icon: 'none' })
}
},
})几个细节值得注意:
_pendingOrder放实例属性而非data:它不需要驱动视图,放进data只会徒增 setData 开销。setData回调里发送:小程序的setData是异步的,setData({ connectionStatus: 'connected' })执行后立刻读this.data.connectionStatus可能还是旧值。回调里才是安全窗口。- 失败路径必须清队列:
onAuthFailed和onReconnectFailed都要清掉_pendingOrder,否则下次连接成功时会把这次的旧数据发出去。 - 页面销毁也要清理:在
onUnload里清掉_pendingOrder,防止内存泄漏。
正解:onShow 只存队列,onAuthSuccess 回调里投递
把"等连接就绪后发送"的逻辑从 onShow 移到 onAuthSuccess 的 setData 回调里,异步重连完成后自动消费队列。失败路径统一清队列并给用户提示,不留悬空数据。
一句话外卖
跨页面传递一次性数据时,消费(delete)和使用必须在同一个可靠的时间点——如果使用依赖异步就绪,就把数据存进"待发队列",让异步完成回调来投递。