From 23906d6478b46fe0ac21f22d21321f9a9f0b9f81 Mon Sep 17 00:00:00 2001 From: Martin Hanzel Date: Thu, 15 Aug 2019 16:43:29 -0400 Subject: Add pubsub messaging system --- app/assets/javascripts/lib/utils/pubsub.js | 36 ++++++++++++++++++++++++++ spec/frontend/lib/utils/pubsub_spec.js | 41 ++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 app/assets/javascripts/lib/utils/pubsub.js create mode 100644 spec/frontend/lib/utils/pubsub_spec.js diff --git a/app/assets/javascripts/lib/utils/pubsub.js b/app/assets/javascripts/lib/utils/pubsub.js new file mode 100644 index 00000000000..6a9da4e8003 --- /dev/null +++ b/app/assets/javascripts/lib/utils/pubsub.js @@ -0,0 +1,36 @@ +/** + * @module message_queue + * + * Implements a simple, async messaging system with pub/sub mechanics. Useful for getting jQuery controls and Vue components to talk to each other. + */ + +const topics = {}; + +/** + * Publish a message to a topic with an optional payload. Listeners are invoked asynchronously. If there are no listeners for a topic, nothing happens. + */ +export function publish(topic, payload) { + const handlers = topics[topic]; + if (!handlers) return; + + handlers.forEach(handler => { + Promise.resolve() + .then(() => handler(payload)) + .catch(e => { + throw e; + }); + }); +} + +/** + * Subscribes to a topic. When a message is published on that topic, the handler will by called. + * @returns {Function} A function that unsubscribes the handler from the topic. + */ +export function subscribe(topic, handler) { + topics[topic] = topics[topic] || []; + topics[topic].push(handler); + + return () => { + topics[topic] = topics[topic].filter(h => h !== handler); + }; +} diff --git a/spec/frontend/lib/utils/pubsub_spec.js b/spec/frontend/lib/utils/pubsub_spec.js new file mode 100644 index 00000000000..244e6d3bbc8 --- /dev/null +++ b/spec/frontend/lib/utils/pubsub_spec.js @@ -0,0 +1,41 @@ +import { publish, subscribe } from '~/lib/utils/pubsub'; + +describe('Pub/sub messaging', () => { + it('sends and receives messages asynchronously', done => { + const receiver1 = jest.fn(); + const receiver2 = jest.fn(); + const receiver3 = jest.fn(); + subscribe('namespace:topic', receiver1); + subscribe('namespace:topic', receiver2); + subscribe('namespace:topic2', receiver3); + + publish('shoudnotreceive', 'shouldnotreceive'); + publish('namespace:topic', 1); + publish('namespace:topic', 2); + publish('namespace:topic2', 3); + + // Receivers should not be called synchronously + expect(receiver1).not.toHaveBeenCalled(); + expect(receiver2).not.toHaveBeenCalled(); + expect(receiver3).not.toHaveBeenCalled(); + + setImmediate(() => { + expect(receiver1.mock.calls).toEqual([[1], [2]]); + expect(receiver2.mock.calls).toEqual([[1], [2]]); + expect(receiver3.mock.calls).toEqual([[3]]); + done(); + }); + }); + + it('allows clients to unsubscribe', done => { + const receiver = jest.fn(); + const unsubscribe = subscribe('topic', receiver); + publish('topic', 1); + unsubscribe(); + publish('topic', 2); + setImmediate(() => { + expect(receiver.mock.calls).toEqual([[1]]); + done(); + }); + }); +}); -- cgit v1.2.1