In software architecture, publish-subscribe is a messaging paradigm in which a sender of a message (called a publisher) does not directly send the message to a specific recipient (called a subscriber). Instead, they divide published messages into different categories without knowing which subscribers, if any, might exist. Similarly, subscribers can express interest in one or more categories and receive only messages of interest without knowing which publishers (if any) exist. (Source: Publish/subscribe wikipedia)
scenario
Publish-subscribe is common for front-end development. The following scenarios actually use the publish-subscribe model.
DOM
In the operationaddEventListener
.Vue
The concept of an event bus in.Node.js
In theEventEmitter
And built-in libraries.
What is publish-subscribe
Publish-subscribe is a one-to-many dependency among objects in which all subscribed objects are notified when an object fires an event.
For example, if you subscribe to a topic on a platform, the system will automatically push information to you when the topic is updated, without you having to manually check it. This example is similar to the publish-subscribe model.
There are a few concepts that need to be clarified
Publisher: Distribute events through the event center
Subscriber: Subscribes to events through the event center
Event center: Is responsible for storing the relationship between events and subscribers.
How to implement a publish-subscribe model
Basic version
Ideas:
- Our event center we use
Map
Structure for storage.Map
In the event namekey
And thevalue
Is to store the corresponding eventsThe subscriber. on
Methods are event subscribed methods that can be based ontype
To find the corresponding set of subscribers to the function we will pass incallback
Add to the collection.emit
Methods are the methods by which events are published, according totype
To find the corresponding set of subscribers and call the subscribers in turn.off
The method is the unsubscribe method, according totype
To find the corresponding set of subscribers, essentially matching the corresponding subscribers according to the reference and moving them out of the set.
The code is as follows:
class EventEmmiter {
subscribes: Map<string.Array<Function> >;constructor() {
this.subscribes = new Map(a); }/** * Event subscription *@param Type Subscribed event name *@param Callback triggers the callback function */
on(type: string, callback: Function) {
const sub = this.subscribes.get(type) | | []; sub.push(callback);this.subscribes.set(type, sub);
}
/** * Release event *@param Type Published event name *@param Args publishes additional arguments to the event */
emit(type: string. args:Array<any>) {
const sub = this.subscribes.get(type) | | [];const context = this;
sub.forEach(fn= > {
fn.call(context, ...args);
})
}
/** * Unsubscribe *@param Type Name of the unsubscribed event *@param Callback Unsubscribes the specific event */
off(type: string, callback: Function) {
const sub = this.subscribes.get(type);
if(sub) {
const newSub = sub.filter(fn= >fn ! == callback);this.subscribes.set(type, newSub); }}}const eventEmmiter = new EventEmmiter();
eventEmmiter.on('eventName'.() = > {
console.log('First Subscription');
});
eventEmmiter.emit('eventName');
const secondEmmiter = (a: number, b: number) = > {
console.log('Second subscription, result is${a + b}`);
}
eventEmmiter.on('eventName', secondEmmiter);
eventEmmiter.emit('eventName'.1.3);
eventEmmiter.off('eventName', secondEmmiter);
eventEmmiter.emit('eventName'.1.3);
Copy the code
The output
First subscription first subscription second subscription is 4 first subscriptionCopy the code
As expected, we now have a publish-subscribe version of the simpler version
Support once subscription
All of us who have used publish-subscribe should know that publish-subscribe direct subscription means that the subscriber is notified only once. Now, let’s add this operation.
Our subscriber needs to have the once attribute for judgment, and after each emit, we need to clear the once event.
With this in mind, we need to change the data structure of our subscribers.
// Previous subscribers (essentially functions)
subscribes: Map<string.Array<Function> >;// Current subscribers
interface SubscribeEvent {
fn: Function;
once: boolean;
}
subscribes: Map<string.Array<SubscribeEvent>>;
Copy the code
At the same time, we find that the logic of once and on only lies in the difference of parameters, and this part is extracted. So we have the following code
interface SubscribeEvent {
fn: Function;
once: boolean;
}
class EventEmmiter {
subscribes: Map<string.Array<SubscribeEvent>>;
constructor() {
this.subscribes = new Map(a); }addEvent(type: string, callback: Function, once: boolean = false) {
const sub = this.subscribes.get(type) | | []; sub.push({fn: callback, once });
this.subscribes.set(type, sub);
}
on(type: string, callback: Function) {
this.addEvent(type, callback);
}
emit(type: string. args:Array<any>) {
const sub = this.subscribes.get(type) | | [];const context = this;
sub.forEach(({ fn }) = >{ fn.call(context, ... args); });const newSub = sub.filter(item= >! item.once);this.subscribes.set(type, newSub);
}
off(type: string, callback: Function) {
const sub = this.subscribes.get(type);
if(sub) {
const newSub = sub.filter(({ fn }) = >fn ! == callback);this.subscribes.set(type, newSub); }}once(type: string, callback: Function) {
this.addEvent(type, callback, true); }}const eventEmmiter = new EventEmmiter();
eventEmmiter.on('eventName'.() = > {
console.log('First Subscription');
});
eventEmmiter.once('eventName'.() = > {
console.log('once test')}); eventEmmiter.emit('eventName');
eventEmmiter.emit('eventName');
Copy the code
Output results:
First subscription Once test First subscriptionCopy the code
Above, our once subscription is also supported.
Release the cache
Based on the above code, we consider a scenario where if no subscriber has subscribed to the event, but our event center publishes the event, then the publication is invalid. This may be acceptable, but in certain scenarios we need to cache published events and call them when a subscriber subscribes.
We use _cacheQueue to cache published events. Since we can publish events with parameters, we need a collection/array to hold those parameters.
type CacheArgs = Array<any>;
_cacheQueue: Map<string.Array<CacheArgs>>;
Copy the code
The final code is as follows
interface SubscribeEvent {
fn: Function;
once: boolean;
}
type CacheArgs = Array<any>;
class EventEmmiter {
subscribes: Map<string.Array<SubscribeEvent>>;
_cacheQueue: Map<string.Array<CacheArgs>>;
constructor() {
this.subscribes = new Map(a);this._cacheQueue = new Map(a); }addEvent(type: string, callback: Function, once: boolean = false) {
const cache = this._cacheQueue.get(type) | | [];if(cache.length ! = =0) {
cache.forEach(args= >{ callback(... args); })this._cacheQueue.delete(type);
}
const sub = this.subscribes.get(type) | | []; sub.push({fn: callback, once });
this.subscribes.set(type, sub);
}
on(type: string, callback: Function) {
this.addEvent(type, callback);
}
emit(type: string. args:Array<any>) {
const sub = this.subscribes.get(type) | | [];if(sub.length === 0) {
const cache = this._cacheQueue.get(type) | | []; cache.push(args)this._cacheQueue.set(type, cache);
} else {
const context = this;
sub.forEach(({ fn }) = >{ fn.call(context, ... args); });const newSub = sub.filter(item= >! item.once);this.subscribes.set(type, newSub); }}off(type: string, callback: Function) {
const sub = this.subscribes.get(type);
if(sub) {
const newSub = sub.filter(({ fn }) = >fn ! == callback);this.subscribes.set(type, newSub); }}once(type: string, callback: Function) {
this.addEvent(type, callback, true); }}const eventEmmiter = new EventEmmiter();
eventEmmiter.emit('test_cache'.1.2);
eventEmmiter.emit('test_cache'.1.3);
eventEmmiter.on('test_cache'.(a: number, b: number) = > {
console.log("Subscribed after the event was published, calculated as",a + b);
});
eventEmmiter.on('test_cache'.(a: number, b: number) = > {
console.log("There is no cache for publish events. No trigger.",a + b);
});
Copy the code
Results:
The calculated value is 3 for those subscribed after the event is published, and the calculated value is 4Copy the code
As expected, a simple publish-subscribe model is in place.
The difference between publish-subscribe and observer patterns
I think the difference may lie in how I understand the publish-subscribe versus observer model.
The publish-subscribe model is a generalized observer pattern, a one-to-many dependency among objects, in which all subscribed objects are notified when an object fires an event.
The publish-subscribe pattern is a commonly used implementation of the observer pattern and is superior to the typical observer pattern in terms of decoupling and reuse.
- In the observer mode, the observer needs to subscribe directly to the target event; After the target issues an event that changes the content, the event is received directly and the response is made.
- In the publish-subscribe model, publishers and subscribers have one more publishing channel; Receiving events from publishers on the one hand and publishing them to subscribers on the other; Subscribers need to subscribe to events from the event channel.
conclusion
The publish-subscribe scenario and simple implementation were described above. For those who want to take a closer look at EventEmitter from Node.js, learn how to use it in your own business scenarios.
advantages
- Modules are decoupled, not strongly associated with specific other modules, and only need to subscribe to related events.
- In asynchronous programming, code can be loosely coupled.
disadvantages
- Loose coupling weakens relationships between objects and can be difficult for programs to trace during debugging.
Finally, thank you for reading this article, it is not very in-depth, there may be some small mistakes or clerical errors, please forgive me, you can comment in the comments section below, thank you very much. In addition, if this article is a little help or inspiration to you, I hope you can click a like ha, support is the motivation of creation ~