Event lifecycle management in JavaScript
Branch: develop
Clone or download
johngeorgewright Merge pull request #68 from johngeorgewright/greenkeeper/babel-plugin…
…-istanbul-5.1.1

Update babel-plugin-istanbul to the latest version 🚀
Latest commit 3795bba Feb 18, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
src
test
.babelrc
.coveralls.yml 💚 Configuring coveralls to use travis correctly Jun 17, 2016
.editorconfig
.eslintignore chore(layout): separate dist files Apr 16, 2018
.eslintrc.js style: change linting convention Aug 9, 2017
.gitignore chore(coverage): switch to nyc Feb 26, 2018
.npmignore chore(layout): separate dist files Apr 16, 2018
.nvmrc
.travis.yml chore(travis): remove chite Aug 30, 2018
CONTRIBUTING.md
LICENSE Initial commit May 27, 2016
README.md docs(README): smelling pisstake Apr 16, 2018
logo.png patch: try to position logo on readme Apr 13, 2017
nodemon.json chore(growl): growl -> growlnotify Feb 27, 2018
package-lock.json chore(package): update lockfile package-lock.json Feb 18, 2019
package.json chore(package): update babel-plugin-istanbul to version 5.1.1 Feb 18, 2019
rollup.config.js ci(fix build script): Aug 30, 2018

README.md

Hot Press

Coverage Status Build Status NPM Version Greenkeeper badge License

Hot Press is an event lifecycle management library for Node.js.

Installation

npm i hot-press

Examples

Standard PubSub

Basic PubSub architecture we're mostly familiar with. Using the on() function, you can add subscribers to events that are published using the emit() function.

import HotPress from 'hot-press';

const {emit, on} = new HotPress();
on('event', (eventName, ...data) => console.log(...data));
emit('event', 'some', 'variables');
// 'some' 'variables'

Subscribing to multiple events

Using the all function you can trigger a subscriber only once all the events have been emitted.

import HotPress from 'hot-press';

const {all, emit} = new HotPress();
all({on: ['event1', 'event2']}, () => console.log('Triggered!'));
emit('event1');
emit('event2');
// 'Triggered!'

Subscription hierarchy

Dots symbolize subscription hierarchy. Using the * operator, you can subscribe to all events under that hierarchy.

import HotPress from 'hot-press';

const {emit, on} = new HotPress();
on('*', e => console.log(2, e));
on('e.*', e => console.log(1, e));
emit('e.f');
// 1 e.f
// 2 e.f

Unsubscribe

Remove all or specific subscribers from events using the off() function.

import HotPress from 'hot-press';

const {emit, off, on} = new HotPress();
const fn = () => console.log('blah');

on('event', fn);
on('event', fn);
on('event', fn);

emit('event');
// blah
// blah
// blah

off('event', fn);
emit('event');
// blah
// blah

off('event');
emit('event');
// <nothing happens>

Subscribing to the beginning and end of events

There are 3 parts to an event lifecycle.

  1. "Before" the event
  2. "On" the event
  3. "After" the event

You can subscribe to any part of the event lifecycle using the appropriate function.

import HotPress from 'hot-press';

const {after, before, emit, off, on} = new HotPress();
before('event', () => console.log(1));
on('event', () => console.log(2));
after('event', () => console.log(3));
emit('event');
// 1
// 2
// 3
off('event');

after('event', () => console.log(3));
before('event', () => console.log(1));
on('event', () => console.log(2));
emit('event');
// 1
// 2
// 3

Asynchronous subscriptions

If your subscription returns a Promise then the next part of the event lifecycle will not be published until the promise has resolved.

Note: It will not delay subscriptions within the same part of the lifecycle, only those that are to be published next. For example, all subscriptions implemented with the on() function will wait until all subscriptions implemented with the before() function have been resolved.

And finally the emit function will return a promise that will resolve once the "after" part of the lifecycle has resolved.

import HotPress from 'hot-press';

const {after, before, emit, on} = new HotPress();
before('event', eventuallyLog(1));
on('event', eventuallyLog(2));
after('event', eventuallyLog(3));
emit('event').then(() => console.log(4));
// 1
// 2
// 3
// 4

function eventuallyLog(num) {
  return () => new Promise(resolve => {
    const log = () => {
      console.log(num);
      resolve();
    };
    setTimeout(log, random());
  });
}

function random(from=0, to=1000) {
  return Math.floor(Math.random() * to) + from;
}

Timeouts

There is a configurable timeout for these asynchronous events. By default it's 300ms, but it can be configured like so:

import HotPress from 'hot-press';

const emitter = new HotPress();
emitter.timeout = 1000; // global
emitter.ns('myNamespace').timeout = 600; // specific to a namespace

If the timeout is exceeded by a listener within any part of event lifecycle, the listener is terminated and an error event is published; it will not kill the event.

Namespacing

You can create a version of hot-press prefixing all messages with a namespace.

import HotPress from 'hot-press';
import {strictEqual} from 'assert';

const {ns} = new HotPress();
const foo = ns('foo');

foo.on('event', eventName => console.log(eventName));
foo.emit('event');
// foo.event

Namespaces can be retrieved uses the dot syntax, or chained ns() calls.

import HotPress from 'hot-press';
import {strictEqual} from 'assert';

const {ns} = new HotPress();
strictEqual(
  ns('nested').ns('namespaces'),
  ns('nested.namespaces'),
  'Namespaces can be retrieved using dots as hierarchy, or chained calls to `ns`'
);

Namespaces also cascade their settings.

import HotPress from 'hot-press';
import {equal} from 'assert';

const {ns} = new HotPress();
const foo = ns('foo');
foo.timeout = 1000;

equal(
  foo.timeout,
  foo.ns('bar').timeout,
  'Namespaces cascade their settings'
);

Error handling

Errors thrown within listeners/subscribers are swallowed but can be captured in error events:

import HotPress from 'hot-press';

const {emit, on} = new HotPress();
on('error.event', error => console.log(error.message));
on('event', () => throw new Error('Something went wrong'));
emit('event');
// "Something went wrong"

Procedures

Procedures are essentially functions, referenced by a string. There are a few reasons you may want to use procedures:

  • Whether the procedure exists or not, you can call it without anything failing. This is useful when using procedures in a plugable interface, when you're unsure whether the procedure is going to be there or not.
  • Hooking in to the function call. Every call to a procedure emits an event with the same name as the procedure. The event lifecycle will exist, which also means you can pause the call with the before() method.
  • If you've decided to use HotPress as a basis to your framework/application it can mean your API is more consistent.

To register a procedure, use the reg() function. To cal your registered procedure, use the call() method.

If the procedure returns a promise, it will wait for the promise to resolve before passing the resolved data back to the caller.

The call() function always returns a promise.

import User from '../lib/user';
import HotPress from 'hot-press';

const {reg, call, on} = new HotPress();

reg('users.get', query => User.all(query));

on('error.users.get', error => console.error(error));

on('users.get', query => console.log('Querying users table', query));

call('users.get', {type: 'admin'}).then(users => {
  // Do something with users
});

Custom Lifecycles

The before(), on() & after() methods don't suffice everyone's needs. Luckily enough, for those people, the lifecycle is configurable.

Note you must still include an on method. Procedures rely on it.

import HotPress from 'hot-press';

const emitter = new HotPress();
emitter.lifecycle = ['foo', 'bar', 'on', 'after'];
const {foo, bar, on, after, emit} = emitter;

foo('event', console.log);
bar('event', console.log);
on('event', console.log);
after('event', console.log);

emit('event');
// foo
// bar
// on
// after

The lifecycle is configurable per namespace too.

import HotPress from 'hot-press';

const {ns} = new HotPress();
const foo = ns('foo');
foo.lifecycle = ['prior', 'on', 'end'];

foo.prior('event', console.log);
foo.on('event', console.log);
foo.end('event', console.log);
foo.emit('event');
// prior
// on
// end

Why the weird name?

Mostly because the good ones were already taken and there's arguably a relation between publishing/subscribing to magazines and papers.

API

after(String eventName, Function subscriber)

Register a subscribing function to the end of the event.

all(Object<[lifecycleName]: String[]> eventNames, Function subscriber)

Register a subscriber for when all events have been published

call(String procedureName, [...Any]) ==> Promise

Call a procedure with the given arguments.

dereg(String procedureName) ==> Number

Deregisters a procedure.

deregAll() ==> Number

Removes all registered procedures.

emit(String eventName, [Any ...data]) ==> Promise

Publishes the event and passes all data arguments directly to the subscribers.

[lifecycleName](String eventName, Function subscriber)

Register a subscribing function to that part of the event lifecycle.

ns(String namespace) ==> HotPress

Creates an object containing the entire API of which all messages/event names will be prefixed with a namespace.

off(String eventName, [Function subscriber]) ==> Number

Removes a given subscriber from an event. If none is specified, it will remove all subscribers from the event.

on(String eventName, Function subscriber)

Register a subscribing function to the event

once(String eventName, Function subscriber)

Registers a subscriber for just one event before it's removed.

once[LifecycleName](String eventName, Function subscriber)

Registers a subscriber, to that part of the event lifecycle, for just one event before it is removed.

triggers(String eventName, Array eventNames)

When the event has been published, publish an array of of other events.

triggers[lifecycleName](String eventName, Function subscriber)

When the event's lifecycle part has been published, publish an array of other events.

reg(String procedureName, Function procedure)

Registers a procedure.