Reactive programming patterns
Software Engineer.
Focused on Node.js and JavaScript.
Here to share my learnings and to learn something new.
Search for a command to run...
Software Engineer.
Focused on Node.js and JavaScript.
Here to share my learnings and to learn something new.
No comments yet. Be the first to comment.
In Node.js, there are 4 primary types of streams: readable, writable, transform, and duplex. In the previous article, we looked at the readable streams in detail. Perhaps you've heard something about writable streams or even used them. But there is a...

In Node.js we have different types of streams, and one of them is the Readable stream. You may have heard of it, or perhaps even used it a few times. But do you know how to use it effectively? This question of efficiency comes when we're dealing with...

Have you ever worked with Node.js streams? What was your experience like? When I first tried to work with streams, I was confused, to say the least. The concept was completely new to me. I thought I could just ignore them, but it turns out they're ev...

Profiling your Node.js applications could be exhausting, especially when you have to switch between different tools to get a full picture of your app's performance. The constant switching of contexts can kill your productivity. What if I tell you tha...

In the previous article, we talked about Atomics in Node.js and the problems they solve in multithreaded programs. While Atomics API is powerful, it is not always convenient to work with it simply because it is just too low level. Other programming l...

We already know what reactive programming is (if you don't, check out this article). In this article, we will look at some of the most common design patterns used in reactive programming and what common design principle they all have that enables the actual reactivity.
Here is the list of the patterns that we are going to explore:
Observable/Observer
Event emitter
Signal
These are the most frequently used ones when it comes to reactive programming and shed some light on the whole picture of this world.
Those two patterns go hand in hand. There is no point in having one without another. The idea behind them is pretty simple. There is a list of clients that want to know about some changes. The Observable can notify them about those changes, but it is required to establish a connection between the Observable and the clients. That's where Observer comes into play. It is basically the contract of the Observable that the client has to fulfill to get notification about the changes.
Here is what a simple observer looks like.
class Observer {
public notify() {}
}
That's right, there is only one method and nothing else.
Observable in this regard looks more complex.
class Observable {
private subscribers: Observer[];
public subscribe() {}
public unsubscribe() {}
public notifyObservers() {}
}
Whenever some client wants to get updates from a particular Observable, he provides an observer interface. It can be a basic function, object, or an observer class.
observable.subscribe(new Observer(...))
observable.subscribe(function() {...})
The important part here is to follow the contractor of the observer that the observable declares. If it only accepts classes, you can't use functions, and vice versa.
Under the hood, the Observable adds the new observer to the list of subscribers.
class Observable {
// other class fields
private subscribers: Observer[];
public subscribe(observer: Observer) {
this.subscribers.push(observer);
}
}
Whenever the Observable updates its value, it notifies each Observer from the list of subscribers.
class Observable {
// other class fields
private subscribers: Observer[];
public notifySubscribers(value) {
// iterates over the the array of subscribers
// and calls notify(value) method with the new value
}
}
The name itself gives us a hint about this pattern. The primary focus of the pattern is events and their handlers. Here is the structure of a simple event emitter.
class EventEmitter {
private eventsMap: Map<string, EventHandler[]>;
public addEventHandler(event, handler) {};
public removeEventHandler(event, handler) {};
public notify(event) {};
}
If someone wants to add a new event/handler to the emitter, he passes an event name alongside the handler that is called whenever this event is fired.
class EventEmitter {
// other class fields
private eventsMap: Map<string, EventHandler[]>;
public addEventHandler(event, handler) {
// 1. check if there is such event in the `eventsMap`
// 2. if there is no such event add it
// 3. add handler for this event
}
}
Whenever this particular event is triggered, all handlers are called one by one.
class EventEmitter {
// other class fields
private eventsMap: Map<string, EventHandler[]>;
public notify(event) {
// 1. Check if event registered in the map
// 2. If there is no such event exit
// 3. If there is such event call its handlers
}
}
Notice that the notify method can accept not only the event itself but also some payload that event handlers of this particular event require.
Signal is a bit different from the previous patterns in terms of structure.
class Signal {
private children: Node;
private value: string;
public getValue() {}
public setValue() {}
}
Basically, it represents a tree, and all subscribers/children of the signal are stored as child nodes of the root node. Whenever the signal has some value updates, it traverses the tree of children to update them respectively.
Note that changes in a particular signal only affect its children but not the whole tree structure. More precisely, it affects all dependent signals. I won't dive any further in this article in this pattern. Maybe there will be a separate one in the future.
While all of the three patterns look different, they actually have the common principle lying behind all of them: store depending entities and update them whenever some action happens. This general abstraction over those patterns allows us to see the picture behind a particular pattern and switch between them simultaneously.
There are multiple patterns used to build the code in a reactive paradigm, such as Observable/Observer, Event emitter, and Signal. We saw their simple implementations and how they function to achieve reactivity. While they look quite different, all of these patterns have a common ground: they all have to store subscribers and notify/update them whenever a new value comes. This high-level abstraction model allows us to see through all the patterns and quickly navigate through them.