npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@r37r0m0d3l/publish_subscribe

v1.4.2

Published

Message bus implementation via publish / subscribe pattern

Downloads

50

Readme

Publish Subscribe

JavaScript's implementation of the Publish-Subscribe pattern.

🗎 Publish Subscribe Documentation 🗎

npm downloads stars types build lgtm


If you use this project don't forget to give a ⭐ star ⭐ to it on GitHub!


🏃💨 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.

👀 Discover more

My other projects