之前寫過一個的事件管理器,就是普通的先訂閱后發布模式。但實際場景中我們需要做到后訂閱的也能收到發布的消息。比如我們關注微信公眾號,還是能看到歷史消息的。類似於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。本文只是闡述了一個事件和方法的流程相關場景,也許對你有啟發。