首页 > 代码库 > 【JavaScript】让事件支持先发布后订阅

【JavaScript】让事件支持先发布后订阅

之前写过一个的事件管理器,就是普通的先订阅后发布模式。但实际场景中我们需要做到后订阅的也能收到发布的消息。比如我们关注微信公众号,还是能看到历史消息的。类似于qq离线消息,我先发给你,你登录了就能收到了。就是确保订阅该事件的方法都能被执行

 var eventManger = {        cached: {},        handlers: {},        //类型,绑定事件         addHandler: function (type, handler) {            if (typeof handler !== "function") return;            if (typeof this.handlers[type] == "undefined") {                this.handlers[type] = [];            }            this.handlers[type].push(handler);            if (this.cached[type] instanceof Array) {                //说明有缓存的 可以执行                handler.apply(null, this.cached[type]);            }        },        removeHandler: function (type, handler) {            var events = this.handlers[type];            for (var i = 0, len = events.length; i < len; i++) {                if (events[i] == handler) {                    events.splice(i, 1);                    break;                }            }        },        trigger: function (type) {            //如果有订阅的事件,这个时候就触发了            if (this.handlers[type] instanceof Array) {                var handlers = this.handlers[type];                var args = Array.prototype.slice.call(arguments, 1);                for (var i = 0, len = handlers.length; i < len; i++) {                    handlers[i].apply(null, args);                }            }            //默认缓存            this.cached[type] = Array.prototype.slice.call(arguments, 1);        }    };

其实就是增加了几行代码。缓存下最后一次触发的时的参数。 然后在addhandle的时候进行判断,如果订阅的时候已经有缓存的参数了,说明该方法可以执行了。

eventManger.addHandler("test", function (res) {    console.log("先订阅,后发布1", res);})eventManger.trigger("test", 2);eventManger.addHandler("test", function (res) {    console.log("先发布,后订阅2", res);})eventManger.addHandler("test", function (res) {    console.log("先发布,后订阅3", res);})

技术分享

我实际的场景是这样的A事件触发之后,才能执行B方法。但B方法需要在C方法完成之后。也就是B依赖于A和C的完成。且A几乎每次都会很快触发,当然可以设两个个开关变量和一个代理函数,等两个事件都完成之后再do B。代码如下:

var aReady = false;var cReady = false;eventManger.addHandler("A", function () {    aReady = true;    console.log("do A");    proxyC();});eventManger.trigger("A", 2);function doB() {    console.log("do B");    //实际B中的方法需要在A事件成功之后才能执行}function doC() {    console.log("do C");    cReady = true;    proxyC();}function proxyC() {    aReady && cReady && doB();}doC();

这样功能是实现了,但是可读性差了,而且事件订阅必须要对位置,如果在trigger之前,doB就永远执行不了,而且代码上多了两个变量和一个方法,最傻的是用一个变量加setTimeout去判断状态,这就可能陷入死循环。

var aReady = false;eventManger.addHandler("A", function () {    aReady = true;    console.log("do A");});function doB() {    console.log("do B");    //实际B中的方法需要在A事件成功之后才能执行}function doC() {    console.log("do C");    if (!aReady) {        console.log("wating...");        setTimeout(doC, 50);        return;    }    doB();}doC();eventManger.trigger("A", 2);//模拟A事件触发迟

技术分享

这种办法最不可取吧。因为外部事件可能挂掉,这儿就走不出去了。等于是挖了个坑。但如果事件支持先发布,后订阅,问题就简单了:

eventManger.trigger("A", 2);function doB() {    console.log("do B");    //实际B中的方法需要在A事件成功之后才能执行}function doC() {    console.log("do c");    eventManger.addHandler("A", function () {        console.log("do a");        doB();    });}doC();

 技术分享

这样就清晰了很多。事件订阅也不必那么在意调用的位置了。以上只是记住最近的一次的调用参数,可以用于后订阅的事件触发。这适合一次性事件(一个周期只会触发一次的事件)。如果是像推送消息的事件,会不断的触发,如果想要确保也能获得全部的历史记录,就需要记住所有的参数。这是一种情况;实际可能还会有更多的流程依赖,当然对于流程控制有很多办法,也有很多库支持。比如promise和async。本文只是阐述了一个事件和方法的流程相关场景,也许对你有启发。

【JavaScript】让事件支持先发布后订阅