前言
发布订阅还是有很广泛的用途的,比如对于单页SPA应用的组件通信,再比如订阅不可控异步任务的执行状况。
当然,开源社区已经有很成熟的发布订阅模式,例如pubsub-js
库,但是他有一个不足就是,不能满足并发发布场景,因为它的发布皆是setTimeout
抛出的,最终无非就是进入宏任务对列依次执行。
这里利用Promise.all模拟一下并发场景;
可以一览它的源码:
我这里稍微的写了一个简陋的发布订阅,可能有很多不足,如果大佬路过,可以矫正我的错误,我只是个实习生,希望各位前辈不吝赐教。
My-PubSub
// ES6 单例模式
class EventBus {
// 单例模式,暴露一个外部公开的接口,确保所有的 new 对象 都引用的是同一份实例
static getInstance() {
if(!EventBus.instance) {
EventBus.instance = new EventBus()
}
return EventBus.instance;
}
constructor() {
// 事件中央解调器
this.EVENT_QUEUE = {};
// 初始化事件token
this.token = null;
// 初始化eventId
this.eventId = -1;
// 初始化开启异步后封装的Promise队列,用于模拟多个发布者并发发布
this.promiseItemArr = [];
}
/**
* @time 2019/12/16 23:35
* @author Eric Wang <vuejs@vip.qq.com>
* @desc 订阅topic
* @param {String} topic -订阅主题
* @param {Function} fn -订阅主题之后的回调,用于接收publish发送的msg
*/
subscribe(topic, fn) {
// eventId自增
++this.eventId;
// Symbol生成唯一token,记录事件队列的每一个订阅者
this.token = String(Symbol.for(this.eventId));
if(!(topic in this.EVENT_QUEUE)) {
this.EVENT_QUEUE[topic] = {};
}
this.EVENT_QUEUE[topic][this.token] = [];
typeof fn === 'function' && this.EVENT_QUEUE[topic][this.token].push(fn);
return this.token;
}
/**
* @time 2019/12/16 23:38
* @author Eric Wang <vuejs@vip.qq.com>
* @desc 发布消息
* @param {String} topic -对应订阅主题
* @param {any} msg -发布的消息,传送给订阅者
* @param {Object} options -发布配置,是否开异步发布
*/
publish(topic, msg, options = {}) {
// 合并配置,配置async,是否开启异步
options = Object.assign({
async: false
}, options);
const topics = this.EVENT_QUEUE[topic];
/**
* @time 2019/12/16 23:27
* @author Eric Wang <vuejs@vip.qq.com>
* @desc 遍历执行每一个topic下的所有订阅者函数
* @param {Array} topic 事件中央解调器里面的每一个主题的所有订阅者对应的回调队列
* @param {Object} msg 发布消息所传送的消息
*/
const executeTopic = (topic, msg) => {
// 如果开启异步
if(options.async) {
topic.forEach(item => {
this.promiseItemArr.push(new Promise(resolve => {
// FIFO先来先执行,先来先发布
setTimeout(() => {
item.call(this, msg);
}, 0);
resolve(true);
}))
});
// Promise模拟并发
Promise.all(this.promiseItemArr)
.catch(err => Promise.reject(err));
} else {
topic.forEach(item => item.call(this, msg));
}
};
// 此处不抛出异常,不妨碍程序运行
if(!(topic in this.EVENT_QUEUE)) {
return false;
}
// 取出一个发布者的所有订阅者
for (let key in topics) {
executeTopic(topics[key], msg);
}
}
/**
* @time 2019/12/16 23:21
* @author Eric Wang <vuejs@vip.qq.com>
* @desc 取消订阅
* @param {String} token -每一个订阅者的token
*/
unsubscribe(token) {
Object.keys(this.EVENT_QUEUE).forEach( item => {
if(token in this.EVENT_QUEUE[item]) {
delete this.EVENT_QUEUE[item];
}
})
}
/**
* @time 2019/12/16 23:23
* @author Eric Wang <vuejs@vip.qq.com>
* @desc 订阅一次once
* @param {String} topic -订阅的主题
* @param {Function} fn -订阅之后的回调fn,用来接收public发送的消息
*/
once(topic, fn) {
const event = this.subscribe(topic, (...rest) => {
this.unsubscribe(event);
fn.apply(this, rest);
});
}
}
End
Emmm,明天还要上班,小菜鸡继续努力搬砖了~