Adding bidirectional to js pub/sub pattern
Pub/sub is a very effective javascript pattern in order to avoid direct access between code modules.
However its one-way mode may be frustrating as we often expect some reply that requires another reverse pub/sub.
Other JS patterns offers bidirectional, but require third-party libraries and heavyer code management.
We suggest improving a tiny javascript pub/sub implementation in order to provide bidirectional facilites.
How does pub/sub pattern work
Some module code 'publishes' a request, supplying topic name and data:
events.publish("users/info", id_user);
Any subscriber to that topic will handle this event:
events.subscribe("users/info", function(data){
...
});
Doing that, both emitting and receiving modules doesn't know anything about respective codes.
Vanilla JS pub/sub implementation
An events object including the subscribe and publish methods is defined inside the global space :
var events = (function(){
var topics = {};
var hOP = topics.hasOwnProperty;
return {
subscribe: function(topic, listener) {
if(!hOP.call(topics, topic)) topics[topic] = [];
var index = topics[topic].push(listener) -1;
return {
remove: function() {
delete topics[topic][index];
}
};
},
publish: function(topic, info) {
if(!hOP.call(topics, topic)) return;
topics[topic].forEach(function(item) {
item(info != undefined ? info : {});
});
}
};
})();
Doing more with less
If the publisher expects an answer, we need to implement another pub/sub where the subscriber will publish and the publisher will subscribe...
Hard coding such a process is not really "doing more with less" !
It would be nice, instead, to just provide some optional callback function to the publish request, in order to handle an incoming response from the subscriber:
events.publish("users/info", id_user, function(response){
...
});
Bidirectional implementation
We can do this, adding a bit of code to the above pub/sub implementation example:
var events = (function(){
var topics = {};
var hOP = topics.hasOwnProperty;
return {
subscribe: function(topic, listener) {
if(!hOP.call(topics, topic)) topics[topic] = [];
var index = topics[topic].push(listener) -1;
return {
remove: function() {
delete topics[topic][index];
}
};
},
publish: function(topic, info, callback) {
if(!hOP.call(topics, topic)) return;
topics[topic].forEach(function(item) {
if(callback){
var UID = String(performance.now()) + Math.random().toString(36).substr(2, 10);
events.subscribe(UID, callback);
var c = function(d){events.publish(UID, d)}
item(info != undefined ? info : {}, c);
}else{
item(info != undefined ? info : {});
}
});
}
};
})();
What did we do ?
Adding an optional callback function argument to the publish request:
publish: function(topic, info, callback) {
Handling it:
if(callback){
var UID = String(performance.now()) + Math.random().toString(36).substr(2, 10);
events.subscribe(UID, callback);
var c = function(d){events.publish(UID, d)}
item(info != undefined ? info : {}, c);
}
If a callback function is provided...
Before emitting its 'publish' request, the code creates an unique topic name (any other unique identifier code may do as well)...
var UID = String(performance.now()) + Math.random().toString(36).substr(2, 10);
...then creates a subscriber dedicated to handle some response using the provided callback function:
events.subscribe(UID, callback);
...then creates an other callback function which can be used by the initial subscriber to publish its response :
var c = function(d){events.publish(UID, d)}
item(info != undefined ? info : {}, c);
Bidirectional is effective now !
The publisher can add a callback function:
events.publish("users/info", id_user, function(response){
// some code handling response...
});
The subscriber can reply:
events.subscribe("users/info", function(data, reply){
//some code generating a response...
reply(response);
});
If no callback function is provided while publishing, the pub/sub works as usual...
Finally
This bidirectional improvement should be easily added to any other pub/sub implementation.
Every subscribe request including a callback function will generate a new topic name that will never be used again. You should improve the code to remove them after use...
Bidirectional pub/sub should be used with only one submitter, otherwise the publisher would get more than one response...