rawjs.xyz

25.08.2023

The PubSub Pattern

A very common requirement when you follow clean code principles or even cases where you do not wish to keep adding code to the original functionality is to add a little bit of extensibility to it.

There's a lot of other patterns like Hooks, and Plugins but we'll go with the pub sub pattern to handle cases where we just need the code to inform others that some part of the work is now done.

This is normally required for cases where you wish to do something after a certain set of work is done.

Code

The code for this is very simple. We just need a place to store the relevant handlers and a way to call them.

 1const handlers = new Map()
 2
 3function subscribe(eventName, handler) {
 4  const currentHandlers = handlers.get(eventName) || []
 5  currentHandlers.push(handler)
 6  handlers.set(eventName, currentHandlers)
 7
 8  // the unsub function
 9  return () => {
10    const currentHandlers = handlers.get(eventName) || []
11    const nextHandlers = currentHandlers.filter(x => x != handler)
12    handlers.set(eventName, nextHandlers)
13  }
14}
15
16function emit(eventName, message) {
17  const currentHandlers = handlers.get(eventName)
18  for (let handler of currentHandlers) {
19    handler(message)
20  }
21}
22
23export const eventManager = {
24  subscribe,
25  emit,
26}

Usage

Let's take a small example of a very common case where you might want to mark a user as active once they have verified their email.

To keep it simple, that's all that this function does. But, we might have additional things we wish to do once the user has been made active.

  • Send a welcome email to the user
  • Let the team that invited the user, know about the joining.
 1function markUserAsActive(id) {
 2  const user = await db.user.update({
 3    is_verified: true,
 4  }).where({
 5    id: id,
 6  });
 7
 8  eventManager.emit("user.update.active", user);
 9}
10
11// somewhere in the imported and running code
12eventManager.subscribe("user.update.active", (user) => {
13  sendWelcomeEmail(user.id);
14});
15
16eventManager.subscribe("user.update.active", (user) => {
17  notifyInvitedTeam(user.id);
18});

You now have separation of concerns and code that you don't need to touch again because it's basically doing one thing, and doing it properly.

You'll see this kind of code in the frontend more than you'll see it in the backend but it's a pattern so it's not tied to anything and can be used anywhere.