发布订阅模式---ES6单例模式+Promise模拟并发发布(FIFO)

1,321 阅读3分钟

前言

发布订阅还是有很广泛的用途的,比如对于单页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,明天还要上班,小菜鸡继续努力搬砖了~