资讯专栏INFORMATION COLUMN

结合promise与websocket的发布/订阅模式实践

vincent_xyb / 2600人阅读

摘要:结合与的发布订阅模式实践本文初衷最近恰好在公司做了一个聊天室系统,所以在系统中做了一下对进行的化改造,所以想写篇文章总结一下,如果大家有什么更好的方法或者心得感悟,欢迎交流技术栈考虑到对并没什么本质影响,所以本文就不涉及了业务场景基于的聊天

结合promise与websocket的发布/订阅模式实践 本文初衷

最近恰好在公司做了一个聊天室系统,所以在系统中做了一下对websocket进行的promise化改造,所以想写篇文章总结一下,如果大家有什么更好的方法或者心得感悟,欢迎交流

技术栈

dva + protobuf
考虑到protobuf对websocket并没什么本质影响,所以本文就不涉及了

业务场景

基于websocket的聊天室系统

开发痛点

可能存在按顺序触发的请求
eg. 删除req---确认删除rsp---刷新页面req---刷新页面rsp
但由于并非所有的删除操作后都会刷新页面,所以考虑是不是可以使用发布订阅模式来模拟类似promise的流式操作

存在同一类型请求短时间内多次触发时,如何寻找每一条回复信息的发射源,考虑可以使用promise池+唯一识别码token来实现

由于dva的异步操作是基于redux-saga的,所以如果可以用promise完成与websocket的互动,那么对于effects中使用yield控制异步流程,也会是一个很好的体验

实现原理

首先,这一套系统的一切前提是请求的唯一标识符token,前端发送给服务器之后,服务器必须要把这个token跟数据放在一起发回来才行

本系统的实现原理是

对websocket的send方法进行封装
发送阶段
1. send执行时,先生成一个promise,及其唯一token
2. 将promise的resolve, reject,token,及其他需要的信息放入一个对象,然后推入一个promise池中
3. 执行websocket的send方法
4. return 这个promise

接收阶段
1. 收到回复消息时,先从promise池中对token进行匹配
2. 根据回复的状态决定执行resolve或reject
3. 其他需要的操作
实现代码
// 每一个实例都只能open一条socket线路,用锁机制防止重复open
// 本例中不使用心跳检测,为了方便,只要close是非主动触发且前端能捕捉到的(如浏览器主动断开,服务器主动断开),都会进行自动重连
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) => {
                this.promisePool = this.promisePool.filter((item) => {
                if (this.tokenCheck(req, rsp) {
                  req.resolve(rsp);
                  return false;
                }
                return true;
              })
            };
        });
    }
    
    close() {
        this.closeConfig.closing = true;
        this._websocket.close();        
    }
    // token包含在content中
    send(name, content) {
        return new Promise((resolve, reject) => {
          this.promisePool.push({
            content,
            resolve,
            reject,
            name
          });
          this._websocket.send({name, content});
    });
}

大概流程就是这样,具体的样例如下

*test () {
    const ws = new MyWebSocket("www.mywebsocket.com");
    yield ws.open();
    yield ws.send(.....).then(()=>{...});
    yield ws.send(.....).then(()=>{...});
}

本文呢大概就是这么多了,如果有什么错误或者遗漏的地方还请大家多多指教

v0.0.2

采取了评论大佬的建议,将promise池从数组改为对象,直接将token做为key,查询起来也非常方便

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});
    });
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94995.html

相关文章

  • 前端知识点

    摘要:原理对处理函数进行延时操作,若设定的延时到来之前,再次触发事件,则清除上一次的延时操作定时器,重新定时。在目标发出内容改变的事件后,直接接收事件并作出响应。首先是目标的构造函数,他有个数组,用于添加观察者。 关于排序 js中sort函数的底层实现机制? js中sort内置多种排序算法,是根据要排序数的乱序程度来决定使用哪一种排序方法。V8 引擎 sort 函数只给出了两种排序 Inse...

    wums 评论0 收藏0
  • GraphQL java工程化实践

    摘要:我在工程实践中直接使用类作为对应实体类的。因此我的结论是,此库并不适用于我的工程实践。工程实践中对其应用方式的考虑在的官方教程中建议针对每请求创建新的实例,查询请求结束则实例们的生命周期结束。 因为自己写过基于react的前端应用,因此一看到GraphQL就被深深吸引,真是直击痛点啊!服务端开发一直是基于java, Spring的,因此开始研究如何在现有工程框架下加入graphql的支...

    MSchumi 评论0 收藏0
  • 2019年7-8月份前端面试题

    摘要:站点接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是无辜的受害者发送的请求。函数内部语句返回的值,会成为方法回调函数的参数。 记录我最近面试缺漏的知识点 css 1.bootstrap如何实现手机PC端自适应 媒体查询 2.flex布局 父容器:(记得常用属性) display:flex flex-direction: row | row-reverse | column ...

    shevy 评论0 收藏0
  • 什么是 HTML 5?

    摘要:该区域代表可以被所控制的画布。那么现在第二个问题,识别该文档,这或许不是大部分用户的需求,但小部分用户并不意味着人数少。因此一个基于的请求于标准内提出。 前言 作为程序员,技术的落实与巩固是必要的,因此想到写个系列,名为 why what or how 每篇文章试图解释清楚一个问题。 这次的 why what or how 主题:现在几乎所有人都知道了 HTML5 ,那么 H5 到底相...

    zhaofeihao 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<