发布订阅管道化

本文介绍发布订阅模式在前端模块化中的应用,通过不同版本的消息中间件实现模块间解耦及数据传递,支持同步与异步操作。

发布订阅作为一种常见的设计模式,在前端模块化领域可以用来解决模块循环依赖问题。

看一个简单的示例

// 消息中间件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);
        });
      }
    }
  }
})();
复制代码

以及消息中间件的使用模块

// 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管道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的话

    // 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包含下述异步代码的话就会出问题

// 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编译

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值