The Publisher/Subscriber Pattern in JavaScript

The Publisher/Subscriber Pattern in JavaScript

In this article, we will be going over the publish/subscribe pattern in JavaScript and see how simple (but powerful) it is to implement in our JavaScript applications.

In this article, we will be going over the publish/subscribe pattern in JavaScript and see how simple (but powerful) it is to implement in our JavaScript applications.

The publisher/subscriber pattern is a design pattern that allows us to create powerful dynamic applications with modules that can communicate with each other without being directly dependent on each other.

The pattern is quite common in JavaScript and has a close resemblance to the observer pattern in the way it works — except that in the observer pattern, an observer is notified directly by its subject, whereas in the publisher/subscriber method, the subscriber is notified through a channel that sits in-between the publisher and subscriber that relays the messages back and forth.

When we implement this, we will need a publisher, subscriber, and some place to store callbacks that are registered from subscribers.

Let’s go ahead and see how this looks like in code. We’re going to use a factory function (you don’t have to use this pattern) to create the publisher/subscriber implementation.

The first thing we’re going to do is to declare a local variable inside the function to store subscribed callbacks:

function pubSub() {
  const subscribers = {}
}

Next, we’ll define the subscribe method which will be responsible for inserting callbacks to subscribers:

function pubSub() {
  const subscribers = {}
  
  function subscribe(eventName, callback) {
    if (!Array.isArray(subscribers[eventName])) {
      subscribers[eventName] = []
    }
    subscribers[eventName].push(callback)
  }
  
  return {
    subscribe,
  }
}

pubSub.js

What’s happening here is that before attempting to register a callback listener for an event name, it checks to see if the eventName property in the subscribers storage is already an array. If it isn't, it assumes that this will be the first registered callback for subscribers[eventName] and initializes it into an array. Then, it proceeds to push the callback into the array.

When the publish event fires, it will take two arguments:

  1. The eventName
  2. Any data that will be passed to *every single callback registered in subscribers[eventName]

Lets go ahead and see how this looks like in code:

function pubSub() {
  
  const subscribers = {}
  
  function publish(eventName, data) {
    if (!Array.isArray(subscribers[eventName])) {
      return
    }
    subscribers[eventName].forEach((callback) => {
      callback(data)
    })
  }
  
  function subscribe(eventName, callback) {
    if (!Array.isArray(subscribers[eventName])) {
      subscribers[eventName] = []
    }
    subscribers[eventName].push(callback)
  }
  
  return {
    publish,
    subscribe,
  }
}

pubSub.js

Before iterating on the list of callbacks in subscribers, it’ll check if it actually exists as an array in the object. If it doesn't, it will assume that the eventName was never even registered before, so it will simply just return. This is a safeguard against potential crashes.

After that, if the program reaches the .forEach line, then we know that the eventName was registered with one or more callbacks in the past. The program will proceed to loop through subscribers[eventName] safely.

For each callback that it encounters, it calls the callback with the data that was passed in as the second argument.

So this is what will happen if we subscribed a function like this:

function showMeTheMoney(money) {
  console.log(money)
}
const ps = pubSub()
ps.subscribe('show-money', showMeTheMoney)

And if we call the publish method sometime in the future:

ps.publish('show-money', 1000000)

Then the showMeTheMoney callback we registered will be invoked in addition to receiving 1000000 as the money argument:

function showMeTheMoney(money) {
  console.log(money) // result: 10000000
}

And that’s how the publisher/subscriber pattern works. We defined a pubSub function and provided a location locally to the function that stores the callbacks, a subscribemethod to register the callbacks, and a publish method that iterates and calls all of the registered callbacks with any data.

There’s one more problem, though. In a real application, we might suffer a never-ending memory leak if we subscribe many callbacks, and it’s especially wasteful if we don’t do anything about that.

So what we need last is a way for subscribed callbacks to be removed when they are no longer necessary. What often happens in this case is that some unsubscribe method is placed somewhere. The most convenient place to implement this is the return value from subscribe because, in my opinion, it’s the most intuitive when we see this in code:

function subscribe(eventName, callback) {
  if (!Array.isArray(subscribers[eventName])) {
    subscribers[eventName] = []
  }
  
  subscribers[eventName].push(callback)
  
  const index = subscribers[eventName].length - 1
  
  return {
    unsubscribe() {
      subscribers[eventName].splice(index, 1)
    },
  }
}

const unsubscribe = subscribe('food', function(data) {
  console.log(`Received some food: ${data}`)
})

// Removes the subscribed callback
unsubscribe()

subscribe.js

In the example, we needed an index. So we make sure we remove the right one since we used .splice, which needs an accurate index to remove the item we are looking for.

You can also do something like this; however, it’s less performant:

function subscribe(eventName, callback) {
  if (!Array.isArray(subscribers[eventName])) {
    subscribers[eventName] = []
  }
  
  subscribers[eventName].push(callback)
  
  const index = subscribers[eventName].length - 1
  
  return {
    unsubscribe() {
      subscribers[eventName] = subscribers[eventName].filter((cb) => {
        // Does not include the callback in the new array
        if (cb === callback) {
          return false
        }
        return true
      })
    },
  }
}

subscribe.js

Disadvantages

Although there are huge benefits to this pattern, there are also devastating disadvantages that might cost us a lot of debugging time.

How do we know if we subscribed the same callback before or not? There’s really no way to tell unless we implement a utility that maps through a list, but then we’d be making JavaScript do more tasks.

It also becomes harder to maintain our code the more we abuse this pattern in a real-world scenario. The fact that callbacks are decoupled in this pattern makes it hard to track down each step when you have callbacks doing this and doing that everywhere.

Conclusion

And that concludes this post. I hope you found this to be valuable and look out for more in the future!

Programming a Javascript Simon Game Tutorial

Programming a Javascript Simon Game Tutorial

In this javascript tutorial, I recorded myself live programming an html5 javascript simon game.

In this javascript tutorial, I recorded myself live programming an html5 javascript simon game.

For those who don't know, I'm a full stack web developer who has been in the industry for over 5 years now. There is a lot of things I have learned along the way and I'd like to share that knowledge with anyone wanting to learn!

like this video if you found it useful and would like to see more videos of the same content.

subscribe to my channel if you are trying to improve your abilities as a web developer, software engineer, or even if you are just learning to code.

Don't forget to turn on those bell notifications!

Understanding Memoization And Dynamic Programming in Javascript

Understanding Memoization And Dynamic Programming in Javascript

In this Javascript tutorial I will explain what memoization is, how to use it, when you should use memoization, how to use memoization, what dynamic programming is, how to use memoization in dynamic programming. Memoization is a big complicated word that you may have never even heard before, but you may be surprised to know that you are most likely already using memoization without even realizing it.

Memoization is a big complicated word that you may have never even heard before, but you may be surprised to know that you are most likely already using memoization without even realizing it. Memoization is just the act of caching values so that they can be calculated quicker in the future. Memoization is really useful in all parts of programming, but where it is most useful is in dynamic programming. In this video I will explain what memoization is, how to use it, and why it is so useful especially in dynamic programming.

🧠 Concepts Covered:

  • What memoization is
  • When you should use memoization
  • How to use memoization
  • What dynamic programming is
  • How to use memoization in dynamic programming

JavaScript Tutorial: if-else Statement in JavaScript

JavaScript Tutorial: if-else Statement in JavaScript

This JavaScript tutorial is a step by step guide on JavaScript If Else Statements. Learn how to use If Else in javascript and also JavaScript If Else Statements. if-else Statement in JavaScript. JavaScript's conditional statements: if; if-else; nested-if; if-else-if. These statements allow you to control the flow of your program's execution based upon conditions known only during run time.

Decision Making in programming is similar to decision making in real life. In programming also we face some situations where we want a certain block of code to be executed when some condition is fulfilled.
A programming language uses control statements to control the flow of execution of the program based on certain conditions. These are used to cause the flow of execution to advance and branch based on changes to the state of a program.

JavaScript’s conditional statements:

  • if
  • if-else
  • nested-if
  • if-else-if

These statements allow you to control the flow of your program’s execution based upon conditions known only during run time.

  • if: if statement is the most simple decision making statement. It is used to decide whether a certain statement or block of statements will be executed or not i.e if a certain condition is true then a block of statement is executed otherwise not.
    Syntax:
if(condition) 
{
   // Statements to execute if
   // condition is true
}

Here, condition after evaluation will be either true or false. if statement accepts boolean values – if the value is true then it will execute the block of statements under it.
If we do not provide the curly braces ‘{‘ and ‘}’ after if( condition ) then by default if statement will consider the immediate one statement to be inside its block. For example,

if(condition)
   statement1;
   statement2;

// Here if the condition is true, if block 
// will consider only statement1 to be inside 
// its block.

Flow chart:

Example:

<script type = "text/javaScript"> 

// JavaScript program to illustrate If statement 

var i = 10; 

if (i > 15) 
document.write("10 is less than 15"); 

// This statement will be executed 
// as if considers one statement by default 
document.write("I am Not in if"); 

< /script> 

Output:

I am Not in if
  • if-else: The if statement alone tells us that if a condition is true it will execute a block of statements and if the condition is false it won’t. But what if we want to do something else if the condition is false. Here comes the else statement. We can use the else statement with if statement to execute a block of code when the condition is false.
    Syntax:
if (condition)
{
    // Executes this block if
    // condition is true
}
else
{
    // Executes this block if
    // condition is false
}


Example:

<script type = "text/javaScript"> 

// JavaScript program to illustrate If-else statement 

var i = 10; 

if (i < 15) 
document.write("10 is less than 15"); 
else
document.write("I am Not in if"); 

< /script> 

Output:

i is smaller than 15
  • nested-if A nested if is an if statement that is the target of another if or else. Nested if statements means an if statement inside an if statement. Yes, JavaScript allows us to nest if statements within if statements. i.e, we can place an if statement inside another if statement.
    Syntax:
if (condition1) 
{
   // Executes when condition1 is true
   if (condition2) 
   {
      // Executes when condition2 is true
   }
}

Example:

<script type = "text/javaScript"> 

// JavaScript program to illustrate nested-if statement 

var i = 10; 

if (i == 10) { 

// First if statement 
if (i < 15) 
	document.write("i is smaller than 15"); 

// Nested - if statement 
// Will only be executed if statement above 
// it is true 
if (i < 12) 
	document.write("i is smaller than 12 too"); 
else
	document.write("i is greater than 15"); 
} 
< /script> 

Output:

i is smaller than 15
i is smaller than 12 too
  • if-else-if ladder Here, a user can decide among multiple options.The if statements are executed from the top down. As soon as one of the conditions controlling the if is true, the statement associated with that if is executed, and the rest of the ladder is bypassed. If none of the conditions is true, then the final else statement will be executed.
if (condition)
    statement;
else if (condition)
    statement;
.
.
else
    statement;


Example:

<script type = "text/javaScript"> 
// JavaScript program to illustrate nested-if statement 

var i = 20; 

if (i == 10) 
document.wrte("i is 10"); 
else if (i == 15) 
document.wrte("i is 15"); 
else if (i == 20) 
document.wrte("i is 20"); 
else
document.wrte("i is not present"); 
< /script> 

Output:

i is 20