发布订阅做为一种常见的设计模式,在前端模块化领域能够用来解决模块循环依赖问题。前端
// 消息中间件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编译模块化