Publish/Subscribe Pattern in Javascript

Published On:
Amer Agovic Programming Language, Javascript
...
drawing

Publish/Subscribe Pattern in Javascript

Amer Agovic No Comments Software Architecture,Javascript

Today I would like to share with you a very useful concept.

It is demonstrated in Javascript but can be applied to any other language as well. In fact this tutorial came about after I recalled how useful it was in ROS (Robot Operating System).  A web application we develop was in need of a similar mechanism.

It is called the Publish/Subscribe Pattern.

Publish/Subscribe Pattern is a construct that facilitates dependency between disparate segments of your application. It becomes really handy when building complex user interfaces.

A smarter user interface is responsive to user input. This creates a dependency between various controls and the usual approach is to intercept change events on said controls and effect modifications on other controls.

The usual approach can easily become unwieldy as the number of rules grows and especially if control structure changes. Here comes Publish/Subscribe Pattern to help.

We streamline application dependencies by using topics. Each topic represents a state or activity of importance. Some segments of the application can subscribe and listen to notifications coming from a topic.

Yet other segments of the application will post notifications if they are semantically related. Now, both sides of the application do not have to know about the structure of the other. All they see and use is a uniform place that consumes and generates topic chatter.

Let us start by defining our own module.

Basic Module Structure

var rlib=(function(){
    return this;
})();

Here we assign the results of an anonymous function to a global variable “rlib”. The place where the publish/subscribe patterns lives from now on is at “rlib”.

Please read my tutorial on hiding scope in Javascript if you are not sure why the above skeleton represents a module.

Into our new module we add code to manage subscriptions and publications.

Subscribing and Publishing

var rlib=(function(){
    // map of registered topics or communication slots
    this.topics={};
    // add subscriber, auto-add topic if needed
    this.subscribe=function(topic,listener){
        if(!topics[topic]) topics[topic]=[];
        topics[topic].push(listener);
        return {
            // return a helper object
            unsubscribe:function(){
                var index=topics[topic].indexOf(listener);
                if(index!==-1) delete topics[topic][index];
            }
        };
    };
    // revert subscription
    this.unsubscribe=function(topic,listener){
        var index=topics[topic].indexOf(listener);
        if(index!==-1) delete topics[topic][index];
    };
    // distribute a topic event to subscribers
    this.publish=function(topic,msg){
        if(!topics[topic]) return;
        topics[topic].forEach(function(item){
            item(msg,topic);
        });
    };
    return this;
})();

A variable is declared that will hold all registered topics. The methods defined are:

  • publish
  • subscribe
  • unsubscribe

The “publish” method takes the topic name and provided message and distributes the message to all subscribers of given topic. The subscribers are expected to be functions that accept two arguments: the message and the topic name. That way one subscriber could listen to multiple topics. If the topic does not exist then no one is listening and so nothing is distributed.

Topics are created on the fly within “subscribe” method. Here if a topic does not exist one is created. Then the subscriber is added and the method returns a helper object that knows how to unsubscribe that particular subscriber.

The complementary method to “subscribe” is “unsubscribe”. It does the reverse by removing a subscriber from a topic. It is that simple. You can use it already.

We can do one more thing…

Publish/Subscribe with Properties

var rlib=(function(){
this.topics={};
this.properties={
    locale_country:'US',
    locale_language:'en',
};
this.subscribe=function(topic,listener){
    if(!topics[topic]) topics[topic]=[];
    topics[topic].push(listener);
    return {
        unsubscribe:function(){
            var index=topics[topic].indexOf(listener);
            if(index!==-1) delete topics[topic][index];
        }
    };
};
this.unsubscribe=function(topic,listener){
    var index=topics[topic].indexOf(listener);
    if(index!==-1) delete topics[topic][index];
};
this.publish=function(topic,msg){
    if(!topics[topic]) return;
    topics[topic].forEach(function(item){
        item(msg,topic);
    });
};
this.set=function(property,value){
    publish(property+".set",value);
    properties[property]=value;
    return this;
};
this.get=function(property){
    return properties[property];
};
this.has=function(property){
    return property in properties;
};
this.setter=function(property,handler){
    Object.defineProperty(this.properties,property,{set:handler});
};
this.getter=function(property,handler){
    Object.defineProperty(this.properties,property,{get:handler});
};
return this;
})();

Now we have added named properties. We can set and get properties and verify if a property exists. Furthermore we can install setter and getter handlers on properties. When a property is set it calls a setter who in turn could be updating a different property.

The properties are making use of topics. Whenever a property is set a topic by the same name plus “.set” is notified. We can set a property and then listen to its topic for modification.

Where all of this goes you can see in the definition of “properties” variable. As an example,  we define two properties for locale.

You could enrich your web application by subscribing to locale topics and adjusting website depending on country or language. The user could be presented with their native language and that can include formatting of dates, currency, numbers.

Locale awareness is just one example. Others include validation, dynamic content etc.

...
Amer Agovic
Amer Agovic, PhD is the President and Founder at Reliancy. He has been the Chief Architect of the Reliancy's own software platform and has considerable experience in structuring and leading Software Development projects. His fields of expertise include Software Architecture, Robotics, Computer Vision and Natural Language Processing.