【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。本文只是闡述了一個事件和方法的流程相關場景,也許對你有啟發。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM