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

armor-pubsub

v0.2.0

Published

publish and subscribe

Downloads

8

Readme

Armor PubSub

ArmorPubSub is a publish/subscribe library written in JavaScript. It can be used in both Browser or NodeJS. It needs no dependencies and can be brought in as an UMD module. It exposes a global variable named ArmorPubSub when it be loaded globally.

ArmorPubSub asynchronously publishes topics by default, you can also synchronously publish topics if you want, there is a so-called invoke queue that is used to sort all publish/unsubscribe to be called in order, so you don't worry about embedding publish/unsubscribe, they behave like asynchronous calls which actually are synchronous in an invoke loop, you can get a promise which would be resolved when an invoke loop is over, or be rejected if an error occurred.

Installation

npm install armor-pubsub

Usage

import ArmorPubSub from "armor-pubsub";

// Assign ArmorPubSub to any object that will can publish/subscribe topic.
let foo = Object.assign({}, ArmorPubSub);

// Make a subscrption to a topic and return an unsubscribe handler which is a function
// that could be called whenever you want to unsubscribe this specific subscription.
let unsubscribe = foo.sub("topic", msg => console.log(msg));

// Publish a topic with a content
foo.pub("topic", "hello world");

// Return a promise which would be resolved when unsubscription has been done.
let promise = unsubscribe();

Terms

namespace

A seperated space to store the subscriptions of topics. The default namespace name is default.

const foo = Object.assign({ id: "foo" }, ArmorPubSub);

foo.sub("hello", callback, { ns: foo.id });

foo.pub("hello", content, { ns: foo.id });

// wrap pub/sub into a specific namespace.
foo.subPrivate = function(topic, callback, options) {
  return this.sub(topic, callback, Object.assign({}, options, { ns: this.id }));
};

foo.pubPrivate = function(topic, content, options) {
  return this.pub(topic, content, Object.assgin({}, options, { ns: this.id }));
};

invoke-queue

You can choose to publish/unsubscribe topic in a synchronous way or an asynchronous way.

ArmorPubSub uses setTimeout to asynchronously invoke callbacks of subscription, so it won't block the current call stack and publish/unsubscribe will be called one-by-one in turn.

The synchronously invoking callbacks is a little more complicated, the publish/unsubscribe might be embedded in another one, the callback might has been removed when iterating the callbacks. So there is a so-called invoke-queue, all of the invoke of the publish/unsubscibe would be pushed into this queue to wait for being executed in turn instead of calling them immediately. The duration between the first invoke pushed into the queue and the last one has been pushed out is called an invoke-loop. In an invoke-loop, every one of calling publish/unsubscribe would return a same promise that won't be resolved until the current invoke-loop has finished.

You can see the steps when using invoke-queue to deal with publish/unsubscribe in the testcase below:

it("Embed publish/subscribe", done => {
  let step = 0; // counter to show the current step.
  let p1, p2, p3, p4;

  // subscribe to topic x.
  foo.sub("x", () => {
    // This line would be met in the first invoke-loop.
    // The publish won't be executed immediately, instead,
    // it would be pushed into invoke-queue and wait for being executed.
    // It returns a promise of the first invoke-loop.
    p2 = foo.pub("y", null, { sync: true });
    p2.then(() => {
      expect(++step).toBe(4);
    });
    // Publish another topic z,
    // it returns the same promise of the first invoke-loop.
    p4 = foo.pub("z");
    expect(p2).toBe(p4);

    expect(++step).toBe(1);
  });

  // subscribe to topic y and save the unsubscribe handler.
  let promiseToDestroy = foo.sub("y", () => {
    expect(++step).toBe(2);
  });

  // subscribe to topic z
  foo.sub("z", () => {
    // As topic z is published asynchronously, this callback would be called by using setTimeout
    expect(++step).toBe(7);
    done();
  });

  expect(p2).toBeUndefined(); // The value of p2 is still undefined.

  // No one has been called until we decide to make a synchronous publish.
  // It returns the promise of the first invoke-loop.
  p1 = foo.pub("x", void 0, { sync: true });
  p1.then(() => {
    expect(++step).toBe(5);
  });
  // Make sure all the promises returned is the same one in a single invoke-loop.
  expect(p1).toBe(p2);

  // The first invoke-loop is over.
  // We call the unsubscribe handler to start another invoke-loop.
  p3 = promiseToDestroy();
  p3.then(() => {
    expect(++step).toBe(6);
  });
  // Make sure the two promises are different.
  expect(p3 === p1).toBeFalsy();

  expect(++step).toBe(3);
});

APIs

pub(topic:string, [content:any], [options:object])

Publish a topic and return a promise that will be resolved when the invoke-loop is over.

  • topic: This argument should be a string, if it given an undefined or null, it would be force to transform to an empty string.
  • content: This optional argument would be passed into callback function provided when subscribing a topic as the first argument.
  • options: This optional argument must be a plain object that can include the keys as below:
    • ns:string: The namespace of topic, if it given an undefined or null, it would be tranformed to default name default.
    • sync:bool: If given true, it will synchronously call callbacks for the topic, otherwise they will be called asynchronously. Default as false.

sub(topic:string, callback:function, [options:object])

Make a subscription to the topic with a callback, return an unsubscribe function which can be called to unsubscribe this specific subscription.

A promise that will be resolved when the invoke-loop is over is returned when calling unsubscribe function.

  • topic: This argument should be a string, if it given an undefined or null, it would be force to transform to an empty string.
  • callback: The function bound to the topic will be called when the topic is published.
  • options: This optional argument must be a plain object that can include the keys as below:
    • ns:string: The namespace of topic, if it given an undefined or null, it would be tranformed to default name default.
    • ctx:object: The context of callback, default value is the current subscriber.

subOnce(topic:string, callback:function, [options:object])

It behaves like sub, but the subscription will be unsubscribed as soon as the topic has once been published.

unsub([topic:string], [callback:function], [options])

Unsubscribe a specific topic or multiple topics filtered by topic, callback, ns and ctx.

The subscriptions of a subscriber will be unsubscribed if they meet the conditions as below:

  • topic: If this is given undefined or null, it will match all topics, otherwise it must match the topic of subscription.
  • callback: If this is given undefined or null, it will match all callbacks, otherwise it must match the callback of subscription.
  • ns: If this is given undefined or null, it will match all namespaces, otherwise it must match the ns of subscription.
  • ctx: if this is given falsy, it will match all ctxs, otherwise it must match the ns of subscription.

hasSub([topic:string], [callback:function], [options])

To find out if a subscription has been made by the subscriber, the signature of hasSub is same as unsub, but it returns true if the first subscription is found, or false if no subscription is found.