發佈訂閱做爲一種常見的設計模式,在前端模塊化領域能夠用來解決模塊循環依賴問題。前端
// 消息中間件v1 var msghub = (function() { var listener = []; return { on: function(type, cb, option) { listener[type] = listener[type] || []; option = option || {}; listener[type].push({ cb: cb, priority: option.priority || 0 }); }, fire: function(type, dataObj) { if (listener[type]) { listener[type].sort((a, b) => a.priority - b.priority).forEach((item) => { item.cb.call(null, dataObj); }); } } } })();
以及消息中間件的使用模塊node
// a.js msghub.on('data', function(data) { console.log(data.val + 1); // 3 }) // b.js msghub.on('data', function(data) { console.log(data.val + 2); // 4 }) // c.js msghub.fire('data', { val: 2 });
當c模塊觸發data事件的時候,a和b模塊的監聽函數都會被執行並輸出相應的結果。linux
上面的例子基本能夠知足需求了,可是有時候咱們但願多個訂閱函數之間能夠傳遞執行結果,相似linux管道a.pipe(b).pipe(c)…這種,上一個函數的輸出是下一個函數的輸入。針對這種管道化需求咱們對msghub的回調遍歷從forEach改成reduce方式,以下代碼所示設計模式
// 消息中間件v2 支持執行結果傳遞 var msghub = (function() { var listener = []; option = option || {}; return { on: function(type, cb, option) { listener[type] = listener[type] || []; listener[type].push({ cb: cb, priority: option.priority || 0 }); }, fire: function(type, dataObj) { if (listener[type]) { listener[type].sort((a, b) => b.priority - a.priority).reduce((pre, cur) => { let result = cur.cb.call(null, pre) || pre; // 若是一個訂閱函數沒有返回值則傳遞上上個訂閱函數的執行結果,若是須要徹底的管道化的話就把|| pre去掉便可 return result; }, dataObj); } } } })();
測試一下上面的msghub瀏覽器
// a.js msghub.on('data', function(data) { console.log('module a get num:' + data.val); // 3 return { val: ++data.val }; }) // b.js msghub.on('data', function(data) { console.log('module b get num:' + data.val) return { val: data.val + 3 } }) // d.js msghub.on('data', function(data) { console.log('module d get num:' + data.val); }) // e.js msghub.on('data', function(data) { console.log('module e get num:' + data.val); }) // c.js msghub.fire('data', { val: 2 });
使用改良後的msghub的話babel
// a.js msghub.on('data', function(data) { console.log('module a get num:' + data.val); // 3 return { val: ++data.val }; }) // b.js msghub.on('data', function(data) { console.log('module b get num:' + data.val) return { val: data.val + 3 } }) // d.js msghub.on('data', function(data) { console.log('module d get num:' + data.val); }) // e.js msghub.on('data', function(data) { console.log('module e get num:' + data.val); }) // c.js msghub.fire('data', { val: 2 });
最終打印輸出以下信息:異步
module a get num:2 module b get num:3 module d get num:6 module e get num:6
上面的例子中有一個問題就是訂閱函數必須是同步代碼,若是a.js包含下述異步代碼的話就會出問題async
// a.js msghub.on('data', function(data) { console.log('module a get num:' + data.val); // 3 return new Promise(function(resolve, reject) { setTimeout(() => { resolve({ val: ++data.val }) }, 1000); }); })
針對可能同步可能異步的狀況咱們須要進一步改良msghub來支持,該請asyn和await出場了模塊化
// 消息中間件v3 支持異步管道化 var msghub = (function() { var listener = []; return { on: function(type, cb, option) { listener[type] = listener[type] || []; option = option || {}; listener[type].push({ cb: cb, priority: option.priority || 0 }); }, fire: function(type, dataObj) { if (listener[type]) { let listenerArr = listener[type].sort((a, b) => b.priority - a.priority); (async function iter() { let val = dataObj; for (const item of listenerArr) { val = await item.cb.call(null, val); } })(); } } } })();
注意: 上述代碼能夠在node環境作測試,若是須要在瀏覽器中運行的話,須要對for of和async await進行babel編譯函數