WebSocket 连接资源管理
1. WebSocket 在什么时候关闭
WebSocket 连接必须在以下任一情况发生时立即关闭,避免连接泄漏和资源浪费:
| 场景 | 说明 |
|---|---|
| 收到预期数据 | 订阅目的达成(如收到目标事件),立即 ws.close() |
| 超时 | 达到预设超时时间,尚未收到预期数据,关闭并结束等待 |
| 连接错误 | error 事件触发,关闭连接并 reject/降级 |
| 外部取消 | 调用方不再需要结果(如组件卸载、用户离开),应能主动中止 |
前三种通常已在实现中处理;外部取消最容易遗漏,导致 Dialog 关闭、页面切换时连接仍保持到超时。
2. 如何关闭
typescript
// 正确的关闭流程
function closeWebSocket(ws: WebSocket, timeoutId: NodeJS.Timeout | number) {
clearTimeout(timeoutId) // 先清除定时器,避免超时回调重复执行
ws.close() // 关闭连接
// 移除 event listeners(可选,close 后通常不再触发)
}
要点:
- •关闭前先
clearTimeout,否则超时回调可能仍会执行 - •关闭后不要再
resolve/reject,避免重复结算 - •多次
ws.close()是幂等的,但应避免在已关闭后继续操作
3. 如何取消:使用 AbortSignal
支持外部取消的标准做法是接受 AbortSignal 参数:
typescript
interface ReceiptWatchOptions {
recipientAddress: string
tokenAddress: string
timeoutMs: number
/** 外部取消信号:abort 时立即关闭 WebSocket 并结束 */
signal?: AbortSignal
}
async function watchForReceipt(options: ReceiptWatchOptions): Promise<ReceiptWatchResult> {
const { signal, timeoutMs } = options
return new Promise((resolve, reject) => {
const ws = new WebSocket(URL)
const cleanup = () => {
clearTimeout(timeoutId)
ws.close()
}
const timeoutId = setTimeout(() => {
cleanup()
resolve({ received: false })
}, timeoutMs)
// 监听外部取消
if (signal) {
if (signal.aborted) {
cleanup()
reject(new DOMException('Aborted', 'AbortError'))
return
}
signal.addEventListener('abort', () => {
cleanup()
reject(new DOMException('Aborted', 'AbortError'))
})
}
ws.addEventListener('error', () => {
cleanup()
reject(new Error('WebSocket 连接失败'))
})
ws.addEventListener('message', (event) => {
if (/* 匹配预期数据 */) {
cleanup()
resolve({ received: true, txHash: '...' })
}
})
})
}
调用方用法(React 示例)
typescript
function useDeposit() {
const abortRef = useRef<AbortController | null>(null)
const deposit = useCallback(async () => {
const ac = new AbortController()
abortRef.current = ac
try {
const result = await verifyDeposit({
signal: ac.signal, // 传入 signal
// ...
})
// ...
} finally {
abortRef.current = null
}
}, [])
// 组件卸载时取消
useEffect(() => {
return () => {
abortRef.current?.abort()
}
}, [])
return { deposit }
}
4. 核心原则
- •WebSocket 是有状态资源,必须显式关闭,不能依赖 GC
- •支持
AbortSignal的异步函数,在组件/流程销毁时应传递 signal 并在 unmount 时abort() - •关闭时要统一清理:
clearTimeout+ws.close(),避免重复 resolve/reject - •超时、错误、成功、取消——四条路径都要走到清理逻辑