New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@byojs/eventer

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@byojs/eventer - npm Package Compare versions

Comparing version 0.1.0 to 0.1.1

2

package.json
{
"name": "@byojs/eventer",
"description": "Event emitter with async-emit and weak-listener support",
"version": "0.1.0",
"version": "0.1.1",
"exports": {

@@ -6,0 +6,0 @@ "./": "./dist/eventer.mjs"

@@ -27,3 +27,3 @@ # Eventer

The main purpose of **Eventer** is to provide a basic event emitter that supports two specific helpful features that most event emitters do not have:
The main purpose of **Eventer** is to provide a basic event emitter that supports two specific helpful features that most event emitters (in JS land) do not have:

@@ -221,3 +221,3 @@ 1. async `emit()`: asynchronous event handling sometimes makes it easier to work around difficult issues with event handling.

Event listener functions are invoked with `this`-context of the emitter instance, *if possible*; `=>` arrow functions never have `this` binding, and already `this`-hard-bound (via `.bind(..)`) functions cannot be `this`-overridden.
Event listener functions are invoked with `this`-context of the emitter instance, *if possible*; `=>` arrow functions never have `this` binding, and already `this`-hard-bound (via `.bind(..)`) functions cannot be `this`-overridden -- and `class` constructors require `new` invocation!

@@ -247,3 +247,3 @@ Event subscriptions must be unique, meaning the event+listener combination must not have already been subscribed. This makes **Eventer** safer, preventing duplicate event subscriptions -- a common bug in event-oriented program design.

A recent welcomed change to the [native `addEventListener(..)` browser API](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is the ability to pass in an [`AbortSignal` instance](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal), from an [`AbortController` instance](https://developer.mozilla.org/en-US/docs/Web/API/AbortController); if the `"abort"` event is fired on the signal, [the event listener is unsubscribed](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#signal), instead of having to manually call [`removeEventListener(..)`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener) to unsubscribe. This is helpful because you don't need keep around any reference to the listener function to unsubscribe it.
A recent welcomed change to the [native `addEventListener(..)` browser API](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is the ability to pass in an [`AbortSignal` instance](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) (from an [`AbortController` instance](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)); if the `"abort"` event is fired, [the associated event listener is unsubscribed](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#signal), instead of having to manually call [`removeEventListener(..)`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener) to unsubscribe. This is helpful because you don't need keep around any reference to the listener function to unsubscribe it.

@@ -267,3 +267,3 @@ **Eventer** also supports this functionality:

**Note:** An `AbortSignal` instance is also held weakly by **Eventer**, so any GC of either the listener or the signal will drop the relationship between them as desired -- without one preventing GC of the other.
**Note:** An `AbortSignal` instance is also held weakly by **Eventer**, so any GC of either the listener or the signal will drop the relationship between them as desired -- without preventing GC of each other.

@@ -276,3 +276,3 @@ ### Inline event listeners (functions)

But before we explain those gotchas, let's highlight the preferred alternative to inline functions (as already implied in previous snippets!):
Before we explain those gotchas, let's highlight the preferred alternatives to inline functions (as already implied in previous snippets!):

@@ -359,3 +359,3 @@ ```js

**Note:** This unsubscription concern is not *fatal*, though. There are [other ways to use `off(..)` unsubscription](#alternate-unsubscription) that avoid this issue.
**Note:** This unsubscription concern is not *unworkable*, though. There are [other ways to use `off(..)` unsubscription](#alternate-unsubscription) that avoid this issue, or you can [use an `AbortSignal` to unsubscribe](#abortsignal-unsubscription).

@@ -366,3 +366,3 @@ #### Accidental unsubscription

Since there is *by definition* no other reference to an inline function reference other than the one passed into `on(..)` / `once(..)`, once the lexical scope (i.e., surrounding function, etc) of the subscription has finished, and its contents are now subject to GC cleanup, the **listener function itself** will likely be GC removed.
Since there is almost certainly no other reference to an inline function reference other than the one passed into `on(..)` / `once(..)`, once the lexical scope (i.e., surrounding function, etc) of the subscription has finished, and its contents are now subject to GC cleanup, the **listener function itself** will likely be GC removed.

@@ -390,2 +390,4 @@ By design, **Eventer**'s [*weak event listeners* mode](WEAK.md) ensures event subscriptions are discarded if the listener itself is GC'd. This helps prevent accidental memory leaks when forgetting to unsubscribe events that are no longer relevant.

Hopefully it's clear that you should avoid inline function listeners, at least when using the *weak event listeners* mode of **Eventer**.
### `once(..)` Method

@@ -539,3 +541,3 @@

**Note:** If you're in the circumstance where all listener(s) have already gone out of scope, and you might be tempted to call `releaseListeners()` (no arguments) to allow the GC, this circumstance is better suited to call `off()` (no arguments) instead.
**Note:** If you're in the circumstance where all listener(s) have already gone out of scope, and you might be tempted to call `releaseListeners()` (no arguments) to allow the GC, this circumstance is better suited to use `off()` (no arguments) instead.

@@ -542,0 +544,0 @@ ## Re-building `dist/*`

@@ -25,4 +25,2 @@ // note: this module specifier comes from the import-map

var runWeakTestsPart1Btn = document.getElementById("run-weak-tests-part-1-btn");
var runWeakTestsPart2Btn = document.getElementById("run-weak-tests-part-2-btn");
var runWeakTestsPart3Btn = document.getElementById("run-weak-tests-part-3-btn");
testResultsEl = document.getElementById("test-results");

@@ -32,4 +30,2 @@

runWeakTestsPart1Btn.addEventListener("click",runWeakTestsPart1);
runWeakTestsPart2Btn.addEventListener("click",runWeakTestsPart2);
runWeakTestsPart3Btn.addEventListener("click",runWeakTestsPart3);

@@ -48,2 +44,4 @@ try {

async function runAutomatedTests() {
cleanupWeakTestsButtons();
testResultsEl.innerHTML = "Running automated tests...<br>";

@@ -125,3 +123,20 @@

false,
false
false,
true,
true,
true,
"A: 23 (true)",
true,
"A: 24 (true)",
true,
"A: 25 (true)",
true,
false,
false,
"A: 28 (true)",
true,
false,
false,
false,
false,
];

@@ -152,4 +167,8 @@

var AC2 = new AbortController();
var AC3 = new AbortController();
var AC4 = new AbortController();
var AS1 = AC1.signal;
var AS2 = AC2.signal;
var AS3 = AC3.signal;
var AS4 = AC4.signal;

@@ -204,2 +223,3 @@ results.push( onFnBound("test",A) );

results.push( events3.off("test",A3) );
results.push( events.on("test",A,{ signal: AS1, }) );

@@ -216,2 +236,18 @@ results.push( events.emit("test",counter++) );

results.push( events.on("test-2",A,{ signal: AS3, }) );
results.push( events.on("test-3",A,{ signal: AS3, }) );
results.push( events.on("test-4",A,{ signal: AS4, }) );
results.push( events.emit("test-2",counter++) );
results.push( events.emit("test-3",counter++) );
results.push( events.emit("test-4",counter++) );
AC3.abort("unsubscribe-3");
results.push( events.emit("test-2",counter++) );
results.push( events.emit("test-3",counter++) );
results.push( events.emit("test-4",counter++) );
results.push( events.off("test-2",A) );
results.push( events.off("test-3",A) );
AC4.abort("unsubscribe-4");
results.push( events.emit("test-4",counter++) );
results.push( events.off("test-4",A) );
if (JSON.stringify(results) == JSON.stringify(expected)) {

@@ -390,2 +426,4 @@ testResultsEl.innerHTML += "(Sync Tests) PASSED.";

async function runWeakTestsPart1() {
cleanupWeakTestsButtons();
testResultsEl.innerHTML = "Running weak tests (part 1)...<br>";

@@ -428,4 +466,6 @@ var expected = [

};
var EController = new AbortController();
var ESignal = EController.signal;
var EController1 = new AbortController();
var ESignal1 = EController1.signal;
var EController2 = new AbortController();
var ESignal2 = EController2.signal;
weakTests.events1 = new Eventer({ asyncEmit: false, weakListeners: true, });

@@ -442,3 +482,4 @@ weakTests.events2 = new Eventer({ asyncEmit: false, weakListeners: false, });

weakTests.finalization.register(weakTests.events3,"events3");
weakTests.finalization.register(ESignal,"E.signal");
weakTests.finalization.register(ESignal1,"E.signal.1");
weakTests.finalization.register(ESignal2,"E.signal.2");

@@ -463,3 +504,5 @@ try {

weakTests.events1.on("test-4",weakTests.listeners.E,{ signal: ESignal, });
weakTests.events1.on("test-4",weakTests.listeners.E,{ signal: ESignal1, });
weakTests.events1.on("test-5",weakTests.listeners.E,{ signal: ESignal1, });
weakTests.events1.on("test-6",weakTests.listeners.E,{ signal: ESignal2, });

@@ -480,8 +523,17 @@ weakTests.results.push( weakTests.events1.emit("test",counter++) );

weakTests.listeners = null;
weakTests.EController = EController;
weakTests.ESignal = ESignal;
weakTests.EController1 = EController1;
weakTests.EController2 = EController2;
weakTests.ESignal1 = ESignal1;
weakTests.ESignal2 = ESignal2;
testResultsEl.innerHTML += "<br><strong>NEXT: Please trigger a GC event in the browser</strong> before running the <em>part 2</em> tests.<br><small>(see instructions above for Chrome or Firefox browsers)<br><br>";
testResultsEl.innerHTML += `
<br><strong>NEXT: Please trigger a GC event in the browser</strong> before running the <em>part 2</em> tests.
<br>
<small>(see instructions above for Chrome or Firefox browsers)</small>
<br>
<button type="button" id="run-weak-tests-part-2-btn">next (part 2) -&gt;</button>
<br><br>
`;
document.getElementById("run-weak-tests-part-2-btn").disabled = false;
document.getElementById("run-weak-tests-part-2-btn").addEventListener("click",runWeakTestsPart2);
return true;

@@ -536,8 +588,17 @@ }

weakTests.results.length = 0;
// allow GC of abort-controller/signal (for part 3)
weakTests.EController = weakTests.ESignal = null;
// allow GC of abort-controllers/signals (for part 3)
weakTests.EController1 = weakTests.ESignal1 =
weakTests.EController2 = weakTests.ESignal2 = null;
testResultsEl.innerHTML += "<br><strong>LASTLY: Please trigger *ONE MORE* GC event in the browser</strong> before running the <em>part 3</em> tests.<br><small>(see instructions above for Chrome or Firefox browsers)<br><br>";
document.getElementById("run-weak-tests-part-3-btn").disabled = false;
testResultsEl.innerHTML += `
<br><strong>LASTLY: Please trigger *ONE MORE* GC event in the browser</strong> before running the <em>part 3</em> tests.
<br>
<small>(see instructions above for Chrome or Firefox browsers)</small>
<br>
<button type="button" id="run-weak-tests-part-3-btn">next (part 3) -&gt;</button>
<br><br>
`;
document.getElementById("run-weak-tests-part-3-btn").addEventListener("click",runWeakTestsPart3);
return true;

@@ -555,3 +616,3 @@ }

finally {
document.getElementById("run-weak-tests-part-2-btn").disabled = true;
cleanupWeakTestsButtons(true,false);
}

@@ -564,3 +625,4 @@ return false;

var expected = [
"removed: E.signal",
"removed: E.signal.1",
"removed: E.signal.2",
];

@@ -591,4 +653,3 @@ weakTests.finalization = null;

finally {
document.getElementById("run-weak-tests-part-2-btn").disabled = true;
document.getElementById("run-weak-tests-part-3-btn").disabled = true;
cleanupWeakTestsButtons();
weakTests = {};

@@ -599,2 +660,19 @@ }

function cleanupWeakTestsButtons(part2 = true,part3 = true) {
if (part2) {
let btn1 = document.getElementById("run-weak-tests-part-2-btn");
if (btn1 != null) {
btn1.disabled = true;
btn1.removeEventListener("click",runWeakTestsPart2);
}
}
if (part3) {
let btn2 = document.getElementById("run-weak-tests-part-3-btn");
if (btn2 != null) {
btn2.disabled = true;
btn2.removeEventListener("click",runWeakTestsPart3);
}
}
}
function timeout(ms) {

@@ -601,0 +679,0 @@ return new Promise(res => setTimeout(res,ms));

# Weak Event Listeners
*Weak event listeners* is a pattern for managing the subscription of events, where the emitter holds a reference to the listener (function) *weakly*. This is a powerful capability, but it requires much more careful attention by the developer to make sure it's being used appropriately.
*Weak event listeners* is a pattern for managing the subscription of events, where the emitter holds a reference to the listener (function) *weakly*. This is a powerful capability, but it requires much more careful attention from the developer to make sure it's being used appropriately.
JS only recently (last few years) gained the ability to properly support *weak event listeners*, which is likely the primary reason that almost no other event emitter implementations besides **Eventer** support this. This capability will probably gain more traction going forward.
JS only recently (in the last few years) gained the ability to properly support *weak event listeners*, which is likely the primary reason that currently, almost no other event emitter implementations besides **Eventer** support this. This useful (but advanced!) capability will probably gain more traction going forward.

@@ -23,5 +23,5 @@ ## Background: Garbage

The classic definition of a "memory leak" means memory that can never be reclaimed. In other words, memory that was allocated in some way, but the handle to that memory has been discarded, and now that memory that can't be de-allocated; the only "solution" is to restart a process (e.g., browser, tab), or even a device.
The classic definition of a "memory leak" means memory that can never be reclaimed. In other words, memory that was allocated in some way, but the handle to that memory has been discarded, and now that memory that can't be de-allocated; the only "solution" is to restart a process (e.g., browser, tab), or even the whole device.
With modern, well-behaving JS engines, true JS program "memory leaks" -- in that classic sense, anyway -- are exceedingly rare. However, JS programs can absolutely *accrete* memory (not technically leaking) throughout their lifetime, where they are accidentally holding onto memory they're no longer using, and the GC isn't able to cleanup for us. This leads to GC prevention.
With modern, well-behaving JS engines, true JS program "memory leaks" -- in that classic sense, anyway -- are exceedingly rare. However, JS programs can absolutely *accrete* memory (not technically *leaking*) throughout their lifetime, where they are accidentally holding onto memory they're no longer using, and the GC isn't able to cleanup for us. This leads to **GC prevention**.

@@ -32,3 +32,3 @@ The most classic example of this is when a large value (array, object) is referenced/used in a function, and that function is registered as an event listener. Even if the program never references that value to use it again, the value is nonetheless kept around, because the JS engine has to assume that possibly, that event might fire to call the listener, where it'd be expecting that value to still be there. This is called "accidental closure".

Explicitly unregistering an no-longer-used event listener is the easiest way to avoid this particular type of GC prevention.
Explicitly unregistering a no-longer-used event listener is the easiest way to avoid this particular type of GC prevention.

@@ -39,5 +39,5 @@ But this is typically challenging in complex apps, to keep track of the appropriate lifetimes of all events.

A quick google search can confirm that "weak event listeners" is not a new idea, or only related to JS. Many other languages/systems have such capabilities, and have relied on them for a long time.
A quick web search will confirm that "weak event listeners" is not a new idea, or only related to JS. Many other languages/systems have such capabilities, and have relied on them for a long time.
JS is still comparitively *brand new* to this trend.
JS is still essentially *brand new* to this trend.

@@ -62,3 +62,3 @@ ### JS Weakness

That means, if you the developer properly clean up (or don't hold in the first place!) any references to listeners, you *don't need* to also unsubscribe them from the event emitter. Once the JS engine GC cleans up those listeners (and closures!), the event emitter will basically "detect" this and implicitly remove the subscriptions automatically.
That means, if you forget to unsubscribe an event emitter, but you properly clean up (or don't hold in the first place!) any references to its listener, the emitter won't prevent the GC of that listener. Once the JS engine GC cleans up those listeners (and closures!), the event emitter will basically "detect" this and implicitly remove its internal subscriptions automatically.

@@ -75,3 +75,3 @@ Usage of a *weak event listener* emitter gives you much finer control over the memory allocation behavior. This capability is a big win, if understood by JS developers, and properly and safely used in their programs.

[As described here](README.md#accidental-unsubscription), if you aren't careful to keep any other references to a listener -- for example, passing only an inline function (e.g., `=>` arrow function) -- the JS engine's GC *will (eventually() do its job*, and clean up those functions/closures (and unsubscribe the events in **Eventer**).
[As described here](README.md#accidental-unsubscription), if you aren't careful to keep any other references to a listener -- for example, passing only an inline function (e.g., `=>` arrow function) -- the JS engine's GC *will (eventually) do its job*, and clean up those functions/closures (and unsubscribe the events in **Eventer**).

@@ -99,8 +99,6 @@ ```js

In practice, this basically means, **never pass inline listener functions** to a *weak event listener* emitter subscription. Moreover, be careful even with inner function declarations, if the enclosing scope might go away via GC.
In practice, this basically means, **never pass inline listener functions** to a *weak event listener* emitter. Moreover, be careful even with inner function declarations, if the enclosing scope might go away via GC.
Always store references to functions used as a event listeners in objects (or classes) that survive beyond single function scopes, or even directly in module/global scope, so the listeners never *accidentally* go away.
When you *want* to cleanup a function no longer being used, you can just unset its value specifically, and let the GC and *weak event listener* capability implicitly clean up the event subscription.
Of course, that doesn't stop you from *also* explicitly unsubscribing events. Both explicit and implicit cleanup here work together to provide a more memory-optimized application design.
Of course, if you can, you should *always* explicitly unsubscribe events. But if for some reason you can't or don't, a weak-event-listener emitter will clean up your mess for you!

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc