Socket.IO 确认回调首参变成 null,检查 timeout 影响了哪次发送
socket.io-client 4.8.4 的 timeout 修改同一实例,标记由下一次 emit 消耗;注册确认时决定签名,连接时机不是唯一原因。
Socket.IO 客户端里,同一个服务端回调明明只返回一个值,某处收到的却是 null,真正的值跑到第二个参数;另一次发送又恢复成一个参数。先查同一个 socket 上是否单独调用过 socket.timeout(1000),然后隔了一段代码才调用 emit()。
在 socket.io-client 4.8.4 中,timeout() 修改当前 socket 的发送 flags,并返回同一个对象。它不是一个独立的超时配置对象;裸调用留下的标记会影响下一次发送,并在那次 emit() 结束时清空。
参数形状在注册确认回调时确定
未使用超时设置时,服务端 callback('pong') 对应普通的单参数确认回调。设置了本次 timeout() 或全局 ackTimeout 后,客户端需要传递超时、断开等错误,成功回调就采用错误优先形式:(null, 'pong')。
对已进入发送缓冲区的消息,这个选择发生在调用 emit()、注册确认回调时,不是连上服务器后实际发送时。因而“连接之前或之后”本身不足以解释参数差异。
没有全局 ackTimeout、且未启用 retries 的普通 emit 路径中,下面的调用关系容易让人误判:
socket.emit('test', (value) => {
console.log(value);
});
socket.timeout(1000);
// 中间经过其他代码后,这一次发送消耗了上面的 timeout 标记
socket.emit('test', (error, value) => {
if (error) return;
console.log(value);
});
socket 是应用已有连接。这不要求两条消息恰好分列 connect 事件两侧;如果别处先发送了消息,标记也可能被那一次消耗。
把超时和对应发送放在一起
对需要超时的消息,紧邻写出调用与错误优先回调:
socket.timeout(1000).emit('test', (error, value) => {
if (error) {
handleAcknowledgementFailure(error);
return;
}
handleAcknowledgement(value);
});
两个 handle 函数代表应用已有处理逻辑。不要把 socket.timeout() 的返回值长期存成“专属发送器”,再与其他代码共享原 socket;4.8.4 返回的仍是同一实例。
配置了全局 ackTimeout 时,所有受它影响的确认回调都应按错误优先方式接收。不能为了让第一个参数重新成为业务值,直接删掉错误参数;发生超时或断开时,那样会把 Error 当成响应。
检查时保留服务端真实确认参数,覆盖连接前缓冲、已连接发送和超时未确认三条路径。再连续发送两次,确认裸 flags 不会泄漏到非预期消息;普通发送与超时发送各自使用正确签名。超时只表示限定时间内没有得到确认,不能据此判定服务端业务完全未执行,重试具有副作用的事件仍需业务去重。


