前言
封装一个带有心跳重连机制的 WebSocket,其好处在于:
- 自动重连机制:当 WebSocket 连接断开或发生错误时,封装的代码会自动进行重连,以保持持续的连接。这样可以提高应用程序的稳定性和可靠性,避免因为网络或服务器问题导致连接中断而无法及时恢复。
- Promise 的使用:通过将 WebSocket 封装为 Promise,可以更方便地使用 Promise 的特性。例如,可以使用
then方法处理连接成功后的逻辑,使用catch方法处理连接失败或发生错误的情况。这样可以使代码更加清晰、易读,并且可以统一处理异步操作的结果和错误。 - 可读性和可维护性:封装带有心跳重连机制的 WebSocket 可以提高代码的可读性和可维护性。通过将连接、重连和事件处理等逻辑分离,使代码结构更清晰。同时,使用 Promise 和相应的错误处理机制,可以更好地处理连接失败和错误,使代码更健壮、易于调试和维护。
下面是示例代码:
Promise封装的WebSocket
export class MyWebSocket {
constructor(url) {
this.url = url;
// close来源判断及后续操做
this.closeConfig = {
resolve: null,
closing: false
}
// promise池
this.promisePool = {};
}
tokenCheck(req, rsp) {
// 此处根据本身的数据结构进行tokenCheck的判断,返回一个boolean
}
open() {
return new Promise((resolve, reject) => {
if (typeof this._websocket === 'undefined') {
this._websocket = new WebSocket(this.url);
this._websocket.open = (e) => {
resolve({e, ws: this});
};
this._websocket.onerror = (e) => {
reject(e);
}
}
this._websocket.onclose = (e) => {
// 非主动close
if (!this.closeConfig.closing) {
console.log('reconnect');
// 对应的重连操做
}
// 若手动close,恢复初始状态
this.closeConfig.closing = false;
}
this._websocket.onmessage = (e) => {
const key = e.content.token;
const req = this.promisePool[key]
req.resolve(e);
delete this.promisePool[key];
};
});
}
close() {
this.closeConfig.closing = true;
this._websocket.close();
}
// token包含在content中
send(name, content) {
return new Promise((resolve, reject) => {
this.promisePool[content.token] = {
content,
resolve,
reject,
name
};
this._websocket.send({name, content});
});
}
async test () {
const ws = new MyWebSocket('www.mywebsocket.com');
await ws.open();
await ws.send(.....).then(()=>{...});
await ws.send(.....).then(()=>{...});
}
带心跳机制的WebSocket
<html>
<head>
<meta charset="utf-8">
<title>WebSocket Demo</title>
</head>
<body>
<script type="text/javascript">
// let ws = new WebSocket("wss://echo.websocket.org");
/*
ws.onerror = function(e) {
console.log('已关闭');
};
ws.onopen = function(e) {
console.log('握手成功');
ws.send('123456789');
}
ws.onclose = function() {
console.log('已关闭');
}
ws.onmessage = function(e) {
console.log('收到消息');
console.log(e);
}
*/
let lockReconnect = false;//避免重复连接
let wsUrl = "wss://echo.websocket.org";
let ws;
let tt;
function createWebSocket() {
try {
ws = new WebSocket(wsUrl);
init();
} catch(e) {
console.log('catch');
reconnect(wsUrl);
}
}
function init() {
ws.onclose = function () {
console.log('链接关闭');
reconnect(wsUrl);
};
ws.onerror = function() {
console.log('发生异常了');
reconnect(wsUrl);
};
ws.onopen = function () {
//心跳检测重置
heartCheck.start();
};
ws.onmessage = function (event) {
//拿到任何消息都说明当前连接是正常的
console.log('接收到消息');
heartCheck.start();
}
}
function reconnect(url) {
if(lockReconnect) {
return;
};
lockReconnect = true;
//没连接上会一直重连,设置延迟避免请求过多
tt && clearTimeout(tt);
tt = setTimeout(function () {
createWebSocket(url);
lockReconnect = false;
}, 4000);
}
//心跳检测
let heartCheck = {
timeout: 3000,
timeoutObj: null,
serverTimeoutObj: null,
start: function(){
console.log('start');
let self = this;
this.timeoutObj && clearTimeout(this.timeoutObj);
this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
this.timeoutObj = setTimeout(function(){
//这里发送一个心跳,后端收到后,返回一个心跳消息,
console.log('55555');
ws.send("123456789");
self.serverTimeoutObj = setTimeout(function() {
console.log(111);
console.log(ws);
ws.close();
// createWebSocket();
}, self.timeout);
}, this.timeout)
}
}
createWebSocket(wsUrl);
</script>
</body>
</html>
结合下来
优化前
export class MyWebSocket {
constructor(url) {
this.url = url;
// close来源判断及后续操作
this.closeConfig = {
resolve: null,
closing: false
};
// promise池
this.promisePool = {};
}
tokenCheck(req, rsp) {
// 此处根据本身的数据结构进行tokenCheck的判断,返回一个boolean
}
open() {
return new Promise((resolve, reject) => {
if (typeof this._websocket === 'undefined') {
this._websocket = new WebSocket(this.url);
this._websocket.onopen = (e) => {
resolve({ e, ws: this });
};
this._websocket.onerror = (e) => {
reject(e);
};
}
this._websocket.onclose = (e) => {
// 非主动close
if (!this.closeConfig.closing) {
console.log('reconnect');
// 对应的重连操作
// 延迟一段时间后重新打开连接
setTimeout(() => {
this.open().then(resolve).catch(reject);
}, 5000); // 5秒延迟
}
// 若手动close,恢复初始状态
this.closeConfig.closing = false;
};
this._websocket.onmessage = (e) => {
const key = e.content.token;
const req = this.promisePool[key];
req.resolve(e);
delete this.promisePool[key];
};
});
}
close() {
this.closeConfig.closing = true;
this._websocket.close();
}
// token包含在content中
send(name, content) {
return new Promise((resolve, reject) => {
this.promisePool[content.token] = {
content,
resolve,
reject,
name
};
this._websocket.send(JSON.stringify({ name, content }));
});
}
}
代码看起来已经相当完善了,但还是有一些可以改进的地方:
- 在
connect()方法中,你可以使用resolve()和reject()的方式来返回 Promise,而不是使用setTimeout和clearTimeout。这样可以更好地控制连接超时的情况。 - 在
reconnect()方法中,你可以通过检查this.websocket.readyState的值来避免重复创建 WebSocket 连接。 - 在
send()方法中,可以将判断连接状态和发送消息的逻辑合并,减少重复代码。
下面是根据上述建议进行改进的代码示例:
export class MyWebSocket {
constructor(url) {
this.url = url;
this.websocket = null;
this.promisePool = {};
this.reconnectInterval = 3000;
this.closeConfig = { resolve: null, closing: false };
}
tokenCheck(req, rsp) {
// 根据自身的数据结构进行 tokenCheck 的判断,返回一个 boolean
}
connect() {
return new Promise((resolve, reject) => {
if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
resolve(this);
} else {
this.websocket = new WebSocket(this.url);
this.websocket.addEventListener('open', () => {
console.log('WebSocket connection established.');
resolve(this);
});
this.websocket.addEventListener('message', (event) => {
const { name, content } = event.data;
const key = content.token;
if (key && this.promisePool[key]) {
const req = this.promisePool[key];
req.resolve(event);
delete this.promisePool[key];
}
});
this.websocket.addEventListener('error', (error) => {
console.error('WebSocket error:', error);
reject(error);
});
this.websocket.addEventListener('close', () => {
console.log('WebSocket connection closed.');
if (!this.closeConfig.closing) {
this.reconnect();
}
this.closeConfig.closing = false;
});
}
});
}
reconnect() {
if (!this.websocket || this.websocket.readyState !== WebSocket.OPEN) {
this.websocket = null; // 清空旧的 WebSocket 对象
setTimeout(async () => {
try {
await this.connect();
} catch (error) {
console.error('WebSocket reconnect failed:', error);
}
}, this.reconnectInterval);
}
}
open() {
return this.connect();
}
close() {
this.closeConfig.closing = true;
this.websocket.close();
}
send(name, content) {
return new Promise((resolve, reject) => {
const sendOrQueue = () => {
if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
this.websocket.send(JSON.stringify({ name, content }));
this.promisePool[content.token] = { content, resolve, reject, name };
} else {
setTimeout(sendOrQueue, 100); // 等待连接建立后再发送消息
}
};
sendOrQueue();
});
}
}