

在现代Web开发中,实时通信已经成为不可或缺的功能。无论是聊天应用、实时数据监控,还是在线协作工具,WebSocket都扮演着重要角色。然而,原生的WebSocket API使用起来相对复杂,缺乏自动重连、错误处理等实用功能。
今天,我将分享如何从零开始打造一个功能完善的WebSocket客户端库 —— websocket-fruge365。
在开始编码之前,我们先明确这个库要解决的问题:
let socket = null;
let handleMessage = null;
let handleErr = null;
let reconnectAttempts = 0;
let maxReconnectAttempts = 5;
let reconnectInterval = 3000;
let isManualClose = false;
let originalUrl = '';
let originalToken = null;function initSocket(url, token = null) {
if (typeof WebSocket === "undefined") {
console.error("初始化失败, 不支持使用WebSocket");
return false;
}
const protocols = token ? [token] : undefined;
try {
socket = new WebSocket(url, protocols);
} catch (error) {
console.error('WebSocket连接创建失败:', error);
return false;
}
// 绑定事件处理器
socket.onopen = socketOnOpen;
socket.onmessage = socketOnMessage;
socket.onerror = socketOnError;
socket.onclose = socketOnClose;
return true;
}socket.onclose = (e) => {
console.log('连接关闭', e.code, e.reason);
if (!isManualClose && reconnectAttempts < maxReconnectAttempts) {
setTimeout(() => {
reconnectAttempts++;
console.log(`尝试重连 (${reconnectAttempts}/${maxReconnectAttempts})`);
initSocket(originalUrl, originalToken);
}, reconnectInterval);
}
};export function connectSocket(url, options = {}) {
if (!url) {
console.error('WebSocket URL不能为空');
return false;
}
const {
token = null,
onMessage = null,
onError = null,
maxReconnectAttempts: maxAttempts = 5,
reconnectInterval: interval = 3000
} = options;
// 设置全局配置
maxReconnectAttempts = maxAttempts;
reconnectInterval = interval;
if (onMessage) handleMessage = onMessage;
if (onError) handleErr = onError;
// 保存原始参数用于重连
originalUrl = url;
originalToken = token;
return initSocket(url, token);
}export function sendMessage(data) {
if (!socket) {
console.error('WebSocket未初始化');
return false;
}
if (socket.readyState === WebSocket.OPEN) {
try {
const message = typeof data === 'string' ? data : JSON.stringify(data);
socket.send(message);
return true;
} catch (error) {
console.error('发送消息失败:', error);
return false;
}
} else {
console.warn('WebSocket连接未就绪, 当前状态:', socket.readyState);
return false;
}
}export function getSocketState() {
if (!socket) return 'CLOSED';
switch (socket.readyState) {
case WebSocket.CONNECTING: return 'CONNECTING';
case WebSocket.OPEN: return 'OPEN';
case WebSocket.CLOSING: return 'CLOSING';
case WebSocket.CLOSED: return 'CLOSED';
default: return 'UNKNOWN';
}
}
export function isConnected() {
return socket && socket.readyState === WebSocket.OPEN;
}为了提供更好的开发体验,我们添加了完整的TypeScript类型定义:
export interface WebSocketOptions {
/** 可选的token参数 */
token?: string;
/** 获取websocket传过来的数据后的处理函数 */
onMessage?: (event: MessageEvent) => void;
/** websocket连接出错后的处理函数 */
onError?: (error: Event) => void;
/** 最大重连次数,默认5次 */
maxReconnectAttempts?: number;
/** 重连间隔,默认3000ms */
reconnectInterval?: number;
}
export type SocketState = 'CONNECTING' | 'OPEN' | 'CLOSING' | 'CLOSED' | 'UNKNOWN';import { connectSocket, sendMessage, closeSocket } from 'websocket-fruge365';
// 连接WebSocket
connectSocket('ws://localhost:8080', {
onMessage: (event) => {
console.log('收到消息:', event.data);
},
onError: (error) => {
console.error('连接错误:', error);
}
});
// 发送消息
sendMessage({ type: 'hello', message: 'Hello WebSocket!' });
// 关闭连接
closeSocket();<script setup>
import { connectSocket, sendMessage, closeSocket } from 'websocket-fruge365';
import { onMounted, onUnmounted } from 'vue';
const initWebSocket = () => {
connectSocket('ws://localhost:8080', {
onMessage: (event) => {
console.log('收到消息:', event.data);
},
onError: (error) => {
console.error('连接错误:', error);
}
});
// 等待连接建立后发送消息
setTimeout(() => {
sendMessage({ type: 'hello', message: 'Hello from Vue3!' });
}, 1000);
}
onMounted(() => {
initWebSocket();
});
onUnmounted(() => {
closeSocket();
});
</script>import { connectSocket, sendMessage, isConnected } from 'websocket-fruge365';
class ChatClient {
constructor(url, userId) {
this.userId = userId;
this.connect(url);
}
connect(url) {
connectSocket(`${url}?userId=${this.userId}`, {
onMessage: this.handleMessage.bind(this),
onError: this.handleError.bind(this),
maxReconnectAttempts: 5,
reconnectInterval: 3000
});
}
handleMessage(event) {
const message = JSON.parse(event.data);
console.log(`${message.user}: ${message.text}`);
}
sendChatMessage(text) {
if (isConnected()) {
sendMessage({
type: 'chat',
user: this.userId,
text: text,
timestamp: Date.now()
});
}
}
}{
"name": "websocket-fruge365",
"version": "1.0.5",
"description": "一个简单易用的WebSocket客户端库,支持自动重连、错误处理和消息管理",
"main": "index.js",
"module": "index.js",
"type": "module",
"files": [
"index.js",
"socket.js",
"README.md",
"types.d.ts"
],
"keywords": [
"websocket",
"socket",
"realtime",
"client",
"reconnect",
"javascript",
"browser",
"nodejs"
],
"author": "fruge365",
"license": "MIT"
}# 登录npm
npm login
# 发布包
npm publish问题:初始连接失败后,重连时token等参数会丢失。
解决方案:在连接时保存原始参数,重连时使用保存的参数。
// 保存原始参数用于重连
originalUrl = url;
originalToken = token;问题:Node.js环境没有原生WebSocket支持。
解决方案:使用ws库,并在文档中说明使用方法。
// Node.js环境
global.WebSocket = require('ws');
import { connectSocket } from 'websocket-fruge365';问题:最初的API设计过于复杂,有params参数等冗余设计。
解决方案:简化API,移除不必要的参数,让用户直接在URL中包含查询参数。
通过这个项目,我学到了:
开发一个npm包是一个很好的学习过程,不仅能加深对技术的理解,还能为开源社区做出贡献。希望这个WebSocket客户端库能帮助到更多的开发者,也欢迎大家提出建议和贡献代码!
如果这个项目对你有帮助,请给个 ⭐ Star 支持一下!
关于作者
我是fruge365,一名热爱技术的前端开发者。专注于Web开发、JavaScript、Vue.js等技术领域。
欢迎关注我的技术分享!