在js的websocket客户端开发中遇到代码割裂情况解决方案
前端  /  管理员 发布于 1年前   346
什么是代码割裂的情况?用一个例子说明。
在 login 方法发送登录,在 onmessage 方法处理登录的返回结果。
这种情况有两个弊端:
1.处理逻辑割裂,一整个逻辑被分割到两个地方处理。
2.上下文割裂,比如登录失败,我需要在登录按钮的边上搞个提示语,
因为处理逻辑被割裂到两个函数,导致通过 onclick 传递的 this,被丢失了。
示例代码:
<style>
div {
margin: 10px;
}
</style>
<div>
<a href="javascript:;" onclick="socket.connect()">打开连接</a>
<a href="javascript:;" onclick="socket.close()">关闭连接</a>
</div>
<div>
<label for="j-token">token</label><input type="text" id="j-token" value="WagCUcmxoUB3brbT">
<a href="javascript:;" onclick="login(this)" id="j-login">登录</a>
</div>
<script>
const socket = {
_ws: null,
_heartbeatIndex: -1,
_onopen: function () {
this._heartbeatIndex = setInterval(function () {
if (!socket._ws) {
if (socket._heartbeatIndex > 0) {
clearTimeout(socket._heartbeatIndex);
socket._heartbeatIndex = -1;
}
return;
}
socket._ws.send('~3yPvmnz~');
}, 45 * 1000);
},
_onmessage: function (event) {
if (event.data === '~u38NvZ~') {
return;
}
//接收服务端的消息,并进行逻辑处理
const router = JSON.parse(event.data);
//处理登录请求的返回
if (router.cmd === cmd.login) {
//因为发送登录的逻辑在login函数,所以拿不到触发登录的按钮,只能重新查找。
let a = document.querySelector('#j-login');
if (router.code === 0) {
a.innerText = '登录成功';
} else {
a.innerText = '登录失败' + router.data;
}
}
},
_onclose: function () {
console.log('WebSocket连接已关闭');
},
_onerror: function (error) {
console.log('WebSocket错误: ' + error);
},
/**
* 发送一条消息到服务端
* @param cmd int
* @param data object|string
*/
send: function (cmd, data) {
if (!this._ws) {
return;
}
if (data instanceof Object) {
data = JSON.stringify(data);
}
let router = {
cmd: cmd,
data: data
};
this._ws.send(JSON.stringify(router));
},
connect: function () {
this._ws = new WebSocket('ws://127.0.0.1:7272');
this._ws.onopen = this._onopen;
this._ws.onmessage = this._onmessage;
this._ws.onclose = this._onclose;
this._ws.onerror = this._onerror;
},
close: function () {
if (this._ws) {
this._ws.close(1000, '主动关闭连接');
this._ws = null;
}
}
};
const cmd = {
login: 3,
};
function login(aThis) {
//这个上下文在websocket对象返回数据时,是拿不到的。
console.log(aThis);
let data = {
token: document.querySelector("#j-token").value,
type: 2
};
socket.send(cmd.login, data);
}
</script>
解决方案:
用 messageChannel 进行通信。
示例代码:
<style>div {margin: 10px;}</style>
<div>
<a href="javascript:;" onClick="socket.connect()">打开连接</a>
<a href="javascript:;" onClick="socket.close()">关闭连接</a>
</div>
<div>
<label for="j-token">token</label><input type="text" id="j-token" value="WagCUcmxoUB3brbT">
<a href="javascript:;" onClick="login(this)">登录</a>
</div>
<script>
const socket = {
_ws: null,
_heartbeatIndex: -1,
_channel: new Map(),
_onopen: function () {
this._heartbeatIndex = setInterval(function () {
if (!socket._ws) {
if (socket._heartbeatIndex > 0) {
clearTimeout(socket._heartbeatIndex);
socket._heartbeatIndex = -1;
}
return;
}
socket._ws.send('~3yPvmnz~');
}, 45 * 1000);
},
_onmessage: function (event) {
if (event.data === '~u38NvZ~') {
return;
}
//这里有个要求,服务端的返回结构必须是:{cmd: int, code: int, data: mixed}
//这里有个缺点:如果同一个cmd,被客户端一次性发送两次,那么第一次的请求的响应是被丢弃了,解决这个问题也很简单。
//我这个只是个demo,就不予解决了。
const router = JSON.parse(event.data);
const channel = socket._channel.get(router.cmd);
socket._channel.delete(router.cmd);
if (!channel instanceof MessageChannel) {
console.log('客户端未知的消息:' + event.data);
return;
}
channel.port2.postMessage(router);
},
_onclose: function () {
console.log('WebSocket连接已关闭');
},
_onerror: function (error) {
console.log('WebSocket错误: ' + error);
},
/**
* 发送一条消息到服务端
* @param cmd int
* @param data object|string
* @param timeout int 服务端响应的超时时间,单位秒
* @returns {Promise<unknown>}
*/
send: async function (cmd, data, timeout = 60) {
if (!this._ws) {
return;
}
if (data instanceof Object) {
data = JSON.stringify(data);
}
let router = {
cmd: cmd,
data: data
};
this._ws.send(JSON.stringify(router));
const channel = new MessageChannel();
this._channel.set(cmd, channel);
return new Promise(function (resolve) {
let setTimeoutIndex = -1;
if (timeout > 0) {
setTimeoutIndex = setTimeout(function () {
socket._channel.delete(cmd);
channel.port1.close();
channel.port2.close();
router.data = '服务端响应超时';
router.code = 500;
resolve(router);
}, 1000 * timeout);
}
channel.port1.onmessage = function (event) {
if (setTimeoutIndex > 0) {
clearTimeout(setTimeoutIndex);
setTimeoutIndex = -1;
}
channel.port1.close();
channel.port2.close();
resolve(event.data);
};
channel.port1.onmessageerror = function (event) {
if (setTimeoutIndex > 0) {
clearTimeout(setTimeoutIndex);
setTimeoutIndex = -1;
}
socket._channel.delete(cmd);
channel.port1.close();
channel.port2.close();
router.data = '客户端socket接收逻辑错误:' + event.data;
router.code = 400;
resolve(router);
};
});
},
connect: function () {
this._ws = new WebSocket('ws://127.0.0.1:7272');
this._ws.onopen = this._onopen;
this._ws.onmessage = this._onmessage;
this._ws.onclose = this._onclose;
this._ws.onerror = this._onerror;
},
close: function () {
if (this._ws) {
this._ws.close(1000, '主动关闭连接');
this._ws = null;
}
}
};
const cmd = {
login: 3,
};
async function login(aThis) {
let data = {
token: document.querySelector("#j-token").value,
type: 2
};
//发送请求,并接收响应
let router = await socket.send(cmd.login, data);
//处理响应
if (router.code === 0) {
aThis.innerText = '登录成功';
} else {
aThis.innerText = '登录失败' + router.data;
}
}
</script>
122 在
学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..123 在
Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..原梓番博客 在
在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..博主 在
佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..1111 在
佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
Copyright·© 2019 侯体宗版权所有·
粤ICP备20027696号