發佈訂閱管道化

發佈訂閱做爲一種常見的設計模式,在前端模塊化領域能夠用來解決模塊循環依賴問題。前端

看一個簡單的示例

// 消息中間件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的話bash

// 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
    });
複製代碼

最終打印輸出以下信息:babel

module a get num:2
module b get num:3
module d get num:6
module e get num:6
複製代碼

訂閱函數支持異步

上面的例子中有一個問題就是訂閱函數必須是同步代碼,若是a.js包含下述異步代碼的話就會出問題異步

// 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出場了async

// 消息中間件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編譯模塊化

相關文章
相關標籤/搜索