WebSocket 实战封装笔记

1. 封装

/utils/ws.ts

1
import qs from 'qs'
2
import store from '@/store'
3
4
/**
5
 * setTimeout 类型
6
 */
7
type Timeout = ReturnType<typeof setTimeout>
8
/**
9
 * setInterval 类型
10
 */
11
type Interval = ReturnType<typeof setInterval>
12
/**
13
 * 允许null的泛型
14
 */
15
type Nullable<T> = T | null
16
/**
17
 * 默认url
18
 */
19
const baseURL = 'ws://test/api'
20
/**
21
 * 默认重连次数
22
 */
23
const reconnectMaxCount = 3
24
/**
25
 * 默认心跳信息
26
 */
27
const message = 'ping'
28
/**
29
 * 默认心跳间隔
30
 */
31
const interval = 3000
32
33
/**
34
 * 默认延时时间
35
 */
36
const timeout = 1000
37
38
type AutoReconnect = {
39
  /**
40
   *重连尝试次数 默认 3
41
   */
42
  reconnectMaxCount?: number
43
}
44
45
type Heartbeat = {
46
  /**
47
   * 心跳信息 默认`ping`
48
   */
49
  message: string
50
  /**
51
   * 心跳间隔时间 默认 `3000` 毫秒
52
   */
53
  interval: number
54
}
55
56
export interface IWSOptions {
57
  /**
58
   * 是否自动重连 默认`true`
59
   */
60
  autoReconnect: boolean | AutoReconnect
61
  /**
62
   * 心跳 默认 `false`
63
   */
64
  heartbeat: boolean | Heartbeat
65
  /**
66
   * url 携带的参数
67
   */
68
  query: Record<string, string>
69
}
70
71
class WS {
72
  url: string
73
  socket: WebSocket | null = null
74
  reconnectCount = 0
75
  delay: Nullable<Timeout> = null
76
  timer: Nullable<Interval> = null
77
  autoReconnect: IWSOptions['autoReconnect']
78
  heartbeat: IWSOptions['heartbeat']
79
  query: IWSOptions['query']
80
81
  constructor(url?: string, options?: IWSOptions) {
82
    const {
83
      autoReconnect = true,
84
      query = {},
85
      heartbeat = false
86
    } = options || {}
87
    this.autoReconnect = autoReconnect
88
    this.heartbeat = heartbeat
89
    this.query = query
90
    // 处理url
91
    this.url =
92
      url ||
93
      baseURL +
94
        qs.stringify(
95
          { token: store.getters.token, ...this.query },
96
          { addQueryPrefix: true }
97
        )
98
    // 开启连接
99
    this.connect()
100
  }
101
102
  /**
103
   * 连接
104
   */
105
  connect(): void {
106
    this.close()
107
    this.socket = new WebSocket(this.url)
108
    this.onError()
109
    this.onOpen()
110
  }
111
112
  /**
113
   * 监听连接
114
   */
115
  onOpen(): void {
116
    if (this.socket) {
117
      this.socket.onopen = () => {
118
        this.send('ping')
119
        // 开启心跳
120
        this.heartbeat && this.startHeartbeat()
121
      }
122
    }
123
  }
124
125
  /**
126
   * 开启心跳
127
   */
128
  startHeartbeat(): void {
129
    const msg = (this.heartbeat as Heartbeat)?.message || message
130
    const int = (this.heartbeat as Heartbeat)?.interval || interval
131
    this.timer = setInterval(() => {
132
      this.send(msg)
133
    }, int)
134
  }
135
136
  /**
137
   * 监听错误
138
   */
139
  onError(): void {
140
    if (this.socket) {
141
      this.socket.onerror = () => {
142
        const count =
143
          (this.autoReconnect as AutoReconnect)?.reconnectMaxCount ||
144
          reconnectMaxCount
145
        if (this.autoReconnect && this.reconnectCount < count) {
146
          this.reconnectCount++
147
          this.connect()
148
        }
149
      }
150
    }
151
  }
152
153
  /**
154
   * 关闭连接
155
   */
156
  close(): void {
157
    this.socket && this.socket.close()
158
    this.delay && clearTimeout(this.delay)
159
    this.timer && clearInterval(this.timer)
160
    this.socket = null
161
  }
162
163
  /**
164
   *  监听消息
165
   * @param callback
166
   */
167
  onMessage(callback: (...data: any[]) => any): void {
168
    if (this.socket) {
169
      this.socket.onmessage = data => {
170
        try {
171
          const res = JSON.parse(data.data)
172
          callback(res)
173
        } catch (err) {
174
          callback(data)
175
        }
176
      }
177
    }
178
  }
179
180
  /**
181
   * 发送消息
182
   * @param data
183
   */
184
  send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void {
185
    if (!this.socket) return
186
    // 状态为 `1-开启状态` 直接发送
187
    if (this.socket.readyState === this.socket.OPEN) {
188
      this.socket.send(JSON.stringify(data))
189
      // 状态为 `0-开启状态` 则延后调用
190
    } else if (this.socket.readyState === this.socket.CONNECTING) {
191
      this.delay = setTimeout(() => {
192
        this.socket?.send(data)
193
      }, timeout)
194
      // 状态为 `2-关闭中 3-关闭状态` 则重新连接
195
    } else {
196
      this.connect()
197
      this.delay = setTimeout(() => {
198
        this.socket?.send(data)
199
      }, timeout)
200
    }
201
  }
202
}
203
204
export default WS

2.使用

1
import WS from '/utils/ws'
2
3
const ws = new WS()
4
5
ws.onMessage(data => {
6
  console.log(data)
7
})

参考链接

  1. https://www.jianshu.com/p/f9aa24ee91dd