AgentSkillsCN

websocket

WebSocket 连接生命周期管理:何时关闭、如何关闭、如何通过 AbortSignal 支持外部取消。Use when implementing WebSocket subscriptions, real-time listeners, or any long-lived WebSocket connections.

中文原作
SKILL.md
--- frontmatter
name: websocket
description: WebSocket 连接生命周期管理:何时关闭、如何关闭、如何通过 AbortSignal 支持外部取消。Use when implementing WebSocket subscriptions, real-time listeners, or any long-lived WebSocket connections.

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
  • 超时、错误、成功、取消——四条路径都要走到清理逻辑