publish-subscribe model
Publish-subscribe pattern and Observer pattern are two common design patterns for handling events and communication. In this article, we will gradually build a fully functional EventEmitter, and through this process we will gain a deep understanding of the publish-subscribe model.
concise version
We build a simple publish-subscribe model:
class EventEmitter {
constructor() {
this.events = {};
}
// 订阅事件
on(event, listener) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(listener);
}
// 卸载事件
off(event, listener) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(l => l !== listener);
}
}
// 发布事件
emit(event, data) {
if (this.events[event]) {
this.events[event].forEach(listener => listener(data));
}
}
}
We created a EventEmitter
class, which includes on
, off
and emit
Methods, respectively used for subscribing events, unloading events and publishing events.
Code flow explanation:
on(event, listener)
: This function is used to subscribe to events. If a specific event does not exist in the event list, we create a new event array and add the incoming listener to that array.off(event, listener)
: This function is used for unloading events. We first check if the event list contains a specific event, and if so, remove the incoming listener from the event array using thefilter
method.emit(event, data)
: This function is used to publish events. If the event list contains a specific event, we iterate through the event array and fire each listener, passing the event data.
Upgraded version: Add one-time call
We introduced a one-time subscription, also known as theonce
method.
// ...
// 一次性订阅
once(event, listener) {
const onceWrapper = data => {
listener(data);
this.off(event, onceWrapper);
};
this.on(event, onceWrapper);
}
Process explanation:
once(event, listener)
: This function allows you to subscribe to events one-time, that is, automatically uninstall the subscription after the event is triggered once. It internally creates a wrapper functiononceWrapper
that executes the one-time listener when the event is triggered, and then automatically unloads the subscription to ensure that the listener is only executed once.
Advanced version: supports continuous calls
Let ourEventEmitter
support continuous calls so that we can execute multiple methods continuously in a chain.
class EventEmitter {
// ...
// 订阅事件,支持连续调用
on(event, listener) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(listener);
return this;
}
// 卸载事件,支持连续调用
off(event, listener) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(l => l !== listener);
}
return this;
}
// 一次性订阅,支持连续调用
once(event, listener) {
const onceWrapper = data => {
listener(data);
this.off(event, onceWrapper);
};
this.on(event, onceWrapper);
return this;
}
}
Process explanation:
on(event, listener)
,off(event, listener)
,once(event, listener)
: In this step, we addedreturn this;
statement, which means that each method TheEventEmitter
instance will be returned after the call. This allows us to call different methods consecutively in a chain of calls, thus improving the readability of the code.
The returns of these three functions allow us to call them consecutively like this:
eventEmitter
.on('event1', listener1)
.on('event2', listener2)
.once('event3', listener3)
.off('event4', listener4);
With consecutive calls, we can easily perform multiple operations in a chain.
Full version: Add event cache
In the fourth step, we introduced the event cache, allowing viewing of subscribed events.
// ...
// 获取事件列表
getEvents() {
return Object.keys(this.events);
}
}
Code flow explanation:
getEvents()
: This function allows you to get a list of subscribed events. It returns an array containing the names of all subscribed events.
The difference between publish-subscribe mode and observer mode
Main differences:
- The publish-subscribe model is more flexible and allows many-to-many relationships between multiple publishers and subscribers. The publisher does not need to know the existence of the subscribers. It usually uses a mediator to handle event distribution.
- The observer pattern is usually a one-to-many relationship, a subject object can have multiple observers, but the interface of the observer is clearly defined. The topic object needs to maintain a list of observers and notify observers when the state changes.
Observer pattern example:
// 主题
class Subject {
constructor() {
this.observers = [];
}
addObserver(observer) {
this.observers.push(observer);
}
removeObserver(observer) {
const index = this.observers.indexOf(observer);
if (index !== -1) {
this.observers.splice(index, 1);
}
}
notify(message) {
this.observers.forEach(observer => observer.update(message));
}
}
// 观察者
class Observer {
update(message) {
console.log(`Received message: ${message}`);
}
}
const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.notify("Hello, observers!");
In our introduction above, the publish-subscribe pattern uses a mediator to handle the publication and subscription of events, while the observer pattern is a one-to-many relationship, and the subject object directly notifies the observer.
Summarize
We introduced the basic publish-subscribe pattern by gradually building an event model that supports continuous invocation. At the same time, the difference between the publish-subscribe mode and the observer mode is briefly introduced. The publish-subscribe mode is more flexible, supports many-to-many relationships, and is suitable for more scenarios, while the observer mode is a one-to-many relationship and clearly defines the interface of the observer. Finally, I hope this article will help everyone understand the publish-subscribe model.