Publish Subscribe
JavaScript's implementation of the Publish-Subscribe pattern.
🗎 Publish Subscribe Documentation 🗎
If you use this project don't forget to give a
🏃 💨 Tl;dr
import { PublishSubscribe } from "@r37r0m0d3l/publish_subscribe";
//
const pubsub: PublishSubscribe = new PublishSubscribe();
type IChannel = number|string|symbol;
const CHANNEL: IChannel = "app";
//
const isCalledOnce: boolean = false;
function syncCallback(data: any, channel: IChannel, token: string): any {
return true;
}
async function asyncCallback(
data: any,
channel: IChannel,
token: string
): any {
return true;
}
const tokenSync: string = pubsub
.subscribe(CHANNEL, syncCallback, isCalledOnce);
const tokenAsync: string = pubsub
.subscribe(CHANNEL, syncCallback, isCalledOnce);
//
const data: any = { someData: "to publish" };
const resultOnly: boolean = true;
const cloneData: boolean = true;
function callback(
results: Array<any | { channel: IChannel; data: any; token?: string }>
): any {}
const resultsSync = pubsub
.publishSync(CHANNEL, data, resultOnly, cloneData, callback);
(async () => {
const resultsAsync = await pubsub
.publishAsync(CHANNEL, data, resultOnly, cloneData);
})();
📦 Usage
Installation.
npm install @r37r0m0d3l/publish_subscribe
CommonJS.
const { PublishSubscribe } = require("@r37r0m0d3l/publish_subscribe");
ECMAScript Modules.
import { PublishSubscribe } from "@r37r0m0d3l/publish_subscribe";
UNPKG CDN.
<script
src="https://unpkg.com/@r37r0m0d3l/publish_subscribe"
></script>
⌨ Creating Instance
const pubsub = new PublishSubscribe();
ℹ Logging
Set log function.
pubsub.setLogging((info) => {
console.group("LOGGING!!!");
console.log(info);
/*
Example when subscribing:
{
channel: "app",
callback: [Function: sync],
once: false,
method: "subscribe"
}
Example when publishing:
{
channel: "app",
data: { data: "the data" },
token: "CETl6gZXkTMhm7JY",
method: "publishSync -> send"
}
*/
console.groupEnd();
});
pubsub.disableLogging();
📛 Naming Channels
Channels can be finite numbers, strings or symbols.
const CHANNEL_NAME_AS_STRING = "TheChannelName";
const CHANNEL_NAME_AS_NUMBER = 123;
const CHANNEL_NAME_AS_SYMBOL = Symbol("ThisChannelLookVeryPrivate");
const CHANNEL = "channel_name";
📩 Subscription
Synchronous subscription.
const tokenOfSynchronous = pubsub
.subscribe("channel_name", function sync(data, channel, token) {
return {
message: "Here can by any kind data for synchronous functions"
};
});
Asynchronous subscription.
const tokenOfAsynchronous = pubsub
.subscribe("channel_name", async (data, channel, token) => {
// Other async call here
return "Here can by any kind data for asynchronous functions";
});
Reusable subscription.
/**
* @param {*} data
* @param {number|string|Symbol} channel
* @param {string} token
*/
function synchronousCallback(data, channel, token) {
// And here is no any data returned if you wish
}
const tokenOne = pubsub.subscribe("channel_name_1", synchronousCallback);
const tokenTwo = pubsub.subscribe("channel_name_2", synchronousCallback);
No need for a token as the subscription will expire after a first successful call.
pubsub.subscribeOnce("channel_exit", (data, channel, _token) => {});
Emit publish when the subscription happened.
pubsub.onSubscribe("channel_name", { message: "This message for every new subscription" });
To disable this.
pubsub.onSubscribeClear("channel_name");
📨 Publishing Events
Intercept all publishing.
pubsub.onPublish((channel, data) => {
// All publishing will be intercepted here
// and you can do your "message matching" here
switch(channel) {
case "app:logout":
pubsub.dropAll();
break;
}
});
pubsub.onPublish(); // now its disabled
Get synchronous results from subscribers.
// Channel name
const channel = "app:start";
// Any kind of data - primitive or object
const data = { data: "the data" };
// Default behavior
const resultOnly = true;
// Prevent published data changes between subscriptions
const cloneData = true;
// Array with results
const results = pubsub
.publishSync(channel, data, resultOnly, cloneData, (results) => {
// Only synchronous functions will be called!
// Array of results can be intercepted in callback
console.log(results);
});
console.log(results); // or can be accessed as function call
Get synchronous and asynchronous results from subscribers.
pubsub
.publishAsync("channel_name", { data: "the data" }, false)
.then((results) => {
// results here also contains
// additional array of data from subscribers
// [ { channel: "app", result: "data", token: "zzroUP97lnxL0VUa" } ]
});
Do not wait for anything from subscribers - use publish
.
pubsub.publish("channel_name", { data: "the data" });
Create sticky
data for the channel. Set sticky
to TRUE is the same as calling onSubscribe
after publish
, publishAsync
, publishSync
.
const channel = "channel_name";
const data = { data: "the data" };
const cloneData = false;
const sticky = true;
pubsub.publish(channel, data, cloneData, sticky);
📪 Unsubscribe
Unsubscribe via token previously retrieved from the subscribe
method.
const token = pubsub.subscribe("channel_name", () => {});
pubsub.unsubscribeByToken(token);
Unsubscribe by channel and callback.
All subscriptions in channel
based on callback
will be removed.
// callback == function someFunction(data, channel, token) {}
pubsub.unsubscribeByChannelAndCallback("channel_name", callback);
This callback
function will be removed from all subscriptions.
pubsub.unsubscribeByCallback(callback);
Unsubscribe based on the parameters provided.
pubsub.unsubscribe(callbackOrChannelOrToken);
pubsub.unsubscribe(channel, callback);
🛠️ Utilities
Retrieve callback function via the token.
// token == "zzroUP97lnxL0VUa"
const callback = pubsub.getCallback(token);
Has any subscriptions.
pubsub.hasSubscription("channel_name");
Has channel.
pubsub.hasChannel("channel_name");
Get a list of channels. An array of numbers, strings, symbols.
pubsub.getChannels();
Drop channel.
pubsub.dropChannel("channel_name");
Clear instance.
pubsub.dropAll();
Potential web browser memory leak fix.
globalThis.addEventListener("beforeunload", function () {
pubsub.dropAll();
});
🤷 About
Yet another publish-subscribe library? Why this library exists:
-
⭐ ⭐ ⭐ -
Preventing published data changes between subscriptions.
-
Possibility to publish events asynchronously.
-
Possibility to do synchronous publishing and receive data from subscriptions.
-
Possibility to use async callbacks in subscriptions.
-
Get subscription callback function by token and use it somewhere else.
-
Error swallowing. The publish-subscribe pattern should not break on some function somewhere deep in code.
-
-
⭐ ⭐ -
Possibility to use finite numbers, strings or symbols as channel names.
-
Subscription functions receive callback token among channel names and published data.
-
Has subscribeOnce method.
-
Has onPublish method for global message matching.
-
-
⭐ -
Custom logging into the console for most actions.
-
TypeScript definitions.
-
Almost dependency-free. This library will work even if the last commit was ten years ago.
-
The worst things that can happen to the publish-subscribe library that is not here:
-
☠️ ☠️ ☠️ -
You should have multiple subscriptions for one channel. Somehow it does not exists in other libraries.
-
Event inheritance. This should be handled by subscription callbacks not by the publish-subscribe system.
-
Hellish wildcards-hierarchical addressing-messages matching for each event. First of all, this is impossible with numbers or symbols. And this makes no sense as you can create the root channel name anyway. In example when you publish
"app:user:registered"
and"app:user:login"
, just publish"app:user"
among others. While when you publish"app:user:re-login"
, do not publish"app:user"
and"re-login"
event will be kept private or unimportant to others.
-
-
☠️ ☠️ -
Possibility to define context for each callback. You have arrow functions for that. You should not save context in one place and take it hostage. Probably you should use global variables instead etc.
-
Cancel publish event distribution for subscribers. This behavior reserved for Observer pattern.
-
-
☠️ -
No order priority for subscriptions. Somehow pattern should be about decoupling, but sometimes it has it in.
-
Sticky events concept. Events will stick in and if any subscriber subscribes for such events after they were published, the subscribers will still receive them upon registration. the subscribers will still receive them upon registration.
-
The things you may not like:
-
🔌 🔌 🔌 - No ECMAScript 3 / ECMAScript 5 / Internet Explorer 6 compatibility. You can transpile the library to the CommonJS module via Babel with the configuration you need.