TLDR: This article introduces the Publisher / Subscriber (Pub/Sub) pattern, a powerful tool for enabling modules in your application to communicate without direct dependencies. While useful, it’s important to avoid overuse, as complete decoupling can lead to unnecessary complexity and bloated code.
Introduction
The Pub/Sub pattern, widely employed in JavaScript applications, offers a way for modules to interact without creating strong interdependencies. However, it’s essential to avoid overusing this pattern, as not all coupling is harmful. Overutilization can result in confusion and code bloat.
Setting up the PubSub Module
The PubSub module will provide the publish
and subscribe
methods. An object is also needed to store the registered callbacks (subscribers).
javascriptCopy code// JavaScript Document - PubSub module
define(function () {
'use strict';
var subscribers = {};
return {
publish: function (topic, data) {
if (!subscribers[topic]) {
return;
}
subscribers[topic].forEach(function (subscriber) {
subscriber(data);
});
},
subscribe: function (topic, callback) {
var index;
if (!subscribers[topic]) {
subscribers[topic] = [];
}
index = subscribers[topic].push(callback) -1;
return {
dispose: function() {
subscribers[topic].splice(index, 1);
}
};
}
};
});
In this module, the publish
function accepts a topic to publish, along with optional data for the subscribers. The subscribe
function allows other modules to register a callback. The returned object exposes the dispose
method, which facilitates unsubscribing from topics.
Setting up Individual Publisher and Subscriber Modules
Next, individual publisher and subscriber modules, conveniently named ModuleA
and ModuleB
, are set up.
ModuleA (subscriber):
javascriptCopy code// JavaScript Document setting up subscription module
define(function (require) {
'use strict';
var pubSub = require('pubSub/pubSub'),
subscription;
subscription = pubSub.subscribe('atopic', function (data) {
console.log('atopic was published with the data: ' + data.something);
subscription.dispose();
});
});
ModuleB (publisher):
javascriptCopy code// JavaScript Document setting up Publisher Module
define(function (require) {
'use strict';
var pubSub = require('pubSub/pubSub');
return {
publishEvent: function () {
var data = {
something: 'some data'
};
pubSub.publish('atopic', data);
}
};
});
ModuleA
subscribes to a topic using the subscribe
method and then disposes of it, while ModuleB
publishes an event to the topic ‘atopic’ and passes a data object to it.
Setting up the Initialization Script
The initialization script is then updated to require these new modules:
javascriptCopy codedefine(function(require) {
'use strict';
return {
init: function() {
var moduleA = require('pubSub/moduleA'),
moduleB = require('pubSub/moduleB');
moduleB.publishEvent();
moduleB.publishEvent();
}
};
});
Adding Initialization to the Main JavaScript File
Lastly, the initialization is added to the main JavaScript file:
javascriptCopy code// JavaScript Document
require (
['factory/init', 'pubSub/init'],
function (factory, pubSub) {
'use strict';
var examples = {
factory: factory,
pubSub: pubSub
};
window.runExample = function (example) {
examples[example].init();
};
}
);
To see the PubSub module in action, attach this main.js file at the bottom of your HTML file’s body, open the browser console and use runExample('pubSub')
.
Conclusion
In this tutorial, we delved into the implementation of the Pub/Sub pattern. This pattern’s key advantage is promoting loose coupling between modules, enhancing code modularity and maintainability. However, remember that it can easily obscure module relationships if overused, leading to confusion and potential code bloat. Use it wisely for optimal application design. Happy coding!