@sedlak.r/mq
v2.0.0
Published
Message queue for events
Readme
EventEmitter
Info
Message queue for events. Is used for sending events, requests, notify information and binding events on dom elements. It is a minimalistic library (< 8kB min file) and is really simple to use it.
How to install
Install Node.js 22 or 24 (LTS lines - jsdom, used by the
test suite, does not support odd-numbered releases). The package manager is pnpm,
pinned by the packageManager field, so let Corepack provide it rather than
installing pnpm yourself:
corepack enable
pnpm installScripts
| Command | What it does |
| ------------------------ | ------------------------------------------------------ |
| pnpm run dev | Serves index.html with the sources loaded as modules |
| pnpm test | Runs the spec suite (Vitest + jsdom) |
| pnpm run test:watch | Same, in watch mode |
| pnpm run test:coverage | Spec suite with a V8 coverage report |
| pnpm run bench | Benchmarks MQ.Store removal cost |
| pnpm run lint | ESLint |
| pnpm run format | Prettier, in place |
| pnpm run build | Writes dist/mq.js and dist/mq.min.js |
How to release
pnpm install- commit changes
- bump the version in
package.jsonand add aCHANGELOG.mdentry pnpm run buildpnpm publishin the root directory
prepublishOnly re-runs lint, tests and the build, so a broken tree cannot be published.
Unlike npm publish, pnpm publish refuses to run with uncommitted changes or
from a branch other than the publish branch. Releasing a prerelease from a
working branch therefore needs the checks turned off explicitly:
pnpm publish --tag rc --no-git-checksMethods
.create(context)
It's method for creating new emitter with given context. Context can be any object od any function.
It is use for manipulation with handlers. More information in section "How to use it"
.event(name, params)
Call event with given name and send through given params. This function is call immediately
after you call it.
.notify(name, params)
Call notify with given name and send through given params. This function is same like .event()
but it can be called after same time. It's only notifier and you cannot determine when it's called, because there can be
timeout due to other events, requests or notify.
.request(name, params)
Call request with given name and send through given params and get return from this call.
This is similar to .event() but this function return value. This is a really special method and EventEmitter check if
it's right called. If .request() has not any handler, than fail because without handler you can not get return value. Call
also fail if there are more handlers than one, because you can not get return value from more functions.
.ctxRequest(name, params)
Same as .request above, but looks up for event with same context as callee
.demand(name, params)
Call request with given name and send through given params and get return from this call.
This is similar to .request() but this function return value and event return undefined. If .demand() has not
any handler, than do not fail and return undefined. Call also fail if there are more handlers than one,
because you can not get return value from more functions.
.subscribe()
This method is used for subscribing for events, that are triggered by .event(), .notify() or .request().
You can use it for your custom events or bind it to dom element. More information in section "How to use it"
.unsubscribe()
This method is used for stop listening for events, that are triggered by .event(), .notify() or .request().
More information in section "How to use it"
Called with no arguments it drops everything registered by that emitter's context - both named events and DOM element bindings. Bindings made by other contexts on the same element are left alone.
Called with only an event name it drops that event's handlers for the context.
Pass the handler too - unsubscribe(name, handler) - to remove one specific
subscription rather than every handler the context has for that event.
Note that on the default context - the global EventEmitter, or an emitter
that was never given a context - remove-by-name ignores both the context and the
handler and drops every handler registered under that name, whichever context
registered it. This is long-standing behaviour of the default context, not
specific to the name-only form. Use EventEmitter.create(context) when you need
removal scoped to your own subscriptions.
.interrupt(event, stopProp, cancelDef)
This function is used to stop browser event. There are parameters for stop propagation and cancel default action. It's helper function.
.watching(name)
This function is used for getting number of listener, who use .subscribe() method. If function return 0, nobody listen
on event and is not necessary to call it.
.ctxWatching(name)
Same as .watching above, but looks up for event with same context as callee
.debugMode(state, filters)
You can turn on debug mode and filter some specific events. Other events will be writen into browser console with all parameters.
How to use it
You can use this library in global way. There is global variable EventEmitter.
Since 1.6 the same API is also importable, and the package ships its own types:
// installs globalThis.MQ and globalThis.EventEmitter, and exports them
import { EventEmitter, Emitter, Store, Timer } from "@sedlak.r/mq";
// identical API, but nothing is written to globalThis
import { EventEmitter } from "@sedlak.r/mq/pure";Both entries share one registry, so an event sent through the imported emitter
reaches handlers subscribed through the global one. require() works too, and
dist/mq.min.js is still the file to drop into a <script src> tag.
The two styles mix freely. A library can bundle its own copy and install the
global, an application can import { EventEmitter } from its own copy, and both
still talk to each other:
import { EventEmitter } from "@sedlak.r/mq";
class Editor {
constructor() {
this.emitter = EventEmitter.create(this);
this.emitter.subscribe("Document.changed", (data) => this.redraw(data));
}
destroy() {
this.emitter.unsubscribe();
}
}That works because the first copy to load publishes its classes and its single
emitter under Symbol.for("@sedlak.r/mq"), and every later copy adopts them
instead of building a second Store. Without it, duplicate copies each get
their own bus and events silently fail to cross - no error, just handlers that
never fire. If the copies are different versions the older one wins, and a
warning names both so the duplicate can be deduplicated.
Example 1
EventEmitter.subscribe("MyEvent", function (data) {
console.log("You handler for event");
});
EventEmitter.event("MyEvent");
EventEmitter.event("MyEvent", [true, "param2"]);In this example you can see basic usages of emitter. You can subscribe for event and call event with or without parameters.
But there are two main problems. You can not destroy this handler for MyEvent, because you don't have original handler and also
you don't have context, so in handler is invalid pointer on this. We can make modification for first problem.
Example 1 - first problem solve
var handler = function (data) {
console.log("You handler for event");
};
EventEmitter.subscribe("MyEvent", handler);
EventEmitter.event("MyEvent");
EventEmitter.event("MyEvent", [true, "param2"]);
EventEmitter.unsubscribe("MyEvent", handler);So now we can unsubscribe our event. Now we must solve the second problem. Context.
Example 1 - second problem solve
var emitter,
context = {},
handler = function (data) {
console.log("You handler for event with context from 'context' variable.");
};
emitter = EventEmitter.create(context);
emitter.subscribe("MyEvent", handler);
emitter.event("MyEvent");
emitter.event("MyEvent", [true, "param2"]);
emitter.unsubscribe("MyEvent", handler);Now we have solve second problem. This is the recommended use of EventEmitter. In every file create new instance with
this as a context. If you implemented destroy on object, you can call emitter.unsubscribe() and all handlers for context
will be destroyed. So we can update last example for complete clean all events.
Example 2
var emitter,
context = {},
handler = function (data) {
console.log("You handler for event wit context from 'context' variable.");
};
emitter = EventEmitter.create(context);
emitter.subscribe("MyEvent", handler);
emitter.subscribe("MyEventSecond", handler);
emitter.event("MyEvent");
emitter.event("MyEvent", [true, "param2"]);
emitter.unsubscribe();And last part is binding to DOM elements. It's easy like others thing before.
Example 3
var emitter,
context = {},
handler = function (data) {
console.log("Clicked on body and destroy.");
emitter.unsubscribe();
};
emitter = EventEmitter.create(context);
emitter.subscribe(document.body, "click", handler);The bare emitter.unsubscribe() above really does detach the click listener.
Before 2.0 it did not, and the binding had to be removed explicitly with
emitter.unsubscribe(document.body, "click", handler).
Licence
MIT License
Copyright (c) 2022 Radek Sedlák
Copyright (c) 2015 Stanislav Hacker
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
The Software shall be used for Good, not Evil.
