Skip to content

Commit f0e25c8

Browse files
committed
docs tweaks/improvements
1 parent 6dadc0a commit f0e25c8

File tree

2 files changed

+21
-21
lines changed

2 files changed

+21
-21
lines changed

README.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ events.emit("update",{ hello: "world" })
2424

2525
## Overview
2626

27-
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:
27+
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:
2828

2929
1. async `emit()`: asynchronous event handling sometimes makes it easier to work around difficult issues with event handling.
3030

@@ -217,7 +217,7 @@ var specialEvent = Symbol("special event");
217217
events.on(specialEvent,onSpecialEvent);
218218
```
219219

220-
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.
220+
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!
221221

222222
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.
223223

@@ -242,7 +242,7 @@ myMap.emit("position-update",centerX,centerY);
242242

243243
### `AbortSignal` unsubscription
244244

245-
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.
245+
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.
246246

247247
**Eventer** also supports this functionality:
248248

@@ -261,15 +261,15 @@ events.on("whatever",onWhatever,{ signal: ac.signal });
261261
ac.abort("Unsubscribe!");
262262
```
263263

264-
**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.
264+
**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.
265265

266266
### Inline event listeners (functions)
267267

268268
It's very common in modern JS programming, and especially with event handling code, to pass inline functions (e.g., `=>` arrow functions) as event listeners. However, there are some very important details/gotchas to be aware of when doing so with **Eventer**.
269269

270270
#### NOT inline event listeners
271271

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

274274
```js
275275
function onWhatever() {
@@ -351,13 +351,13 @@ events.on(
351351
events.off("whatever", /* OOPS, what do I pass here!? */)
352352
```
353353

354-
**Note:** This unsubscription concern is not *fatal*, though. There are [other ways to use `off(..)` unsubscription](#alternate-unsubscription) that avoid this issue.
354+
**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).
355355

356356
#### Accidental unsubscription
357357

358358
The most pressing concern with inline event listeners arises when using the [*weak event listeners* mode](WEAK.md). Since this is the *default* mode of **Eventer**, it's of particular importance to be aware of this *very likely* gotcha.
359359

360-
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.
360+
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.
361361

362362
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.
363363

@@ -380,6 +380,8 @@ listenToWhatever();
380380

381381
After the call to `listenToWhatever()`, any `"whatever"` events fired, may be handled or not, unpredictably, because the inner `=>` arrow function is now subject to GC cleanup at any point the JS engine feels like it!
382382

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

385387
The `once(..)` method subscribes like [`on(..)`](#on-method), except that as soons as the event is emitted the first time, the listener is unsubscribed. This guarantees a specific event+listener will first *at most* "once".
@@ -528,7 +530,7 @@ In other words, it's a way to opt-in to [*weak event listeners* mode](WEAK.md),
528530

529531
This differs from calling `off(null,onWhatever)` / `off()` in that `releaseListeners()` *does not* affirmatively unsubscribe the events (as `off(..)` does), but merely *allow* future implicit unsubscription.
530532

531-
**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.
533+
**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.
532534

533535
## Re-building `dist/*`
534536

WEAK.md

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Weak Event Listeners
22

3-
*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.
3+
*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.
44

5-
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.
5+
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.
66

77
## Background: Garbage
88

@@ -20,23 +20,23 @@ From the JS code perspective, all you need to do to *cleanup* is to unset that l
2020

2121
### Memory "Leaks"
2222

23-
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.
23+
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.
2424

25-
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.
25+
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**.
2626

2727
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".
2828

2929
Even if the program intentionally unsets all its own references to that function (closure), an event emitter would typically hold a *strong* reference to that listener function, and thus prevent its GC (and the GC of the large array/object value).
3030

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

3333
But this is typically challenging in complex apps, to keep track of the appropriate lifetimes of all events.
3434

3535
### Precedence
3636

37-
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.
37+
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.
3838

39-
JS is still comparitively *brand new* to this trend.
39+
JS is still essentially *brand new* to this trend.
4040

4141
### JS Weakness
4242

@@ -56,7 +56,7 @@ But it's a very nascent area of capability for JS, given the feature newness. Mo
5656

5757
By weakly holding event listeners, the GC prevention (by "accidental closure") problem discussed above is more likely avoided. The emitter instance **DOES NOT** prevent the listener function -- and particularly, anything the function has a closure over! -- from being cleaned up by GC (garbage collection).
5858

59-
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.
59+
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.
6060

6161
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.
6262

@@ -68,7 +68,7 @@ As a wise grandpa once said:
6868
6969
The downside (err... *weakness*) of a *weak event listener* emitter is that it's possible, depending on the habits of how you use it, to create very unpredictable behavior (and maddening program bugs!).
7070

71-
[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**).
71+
[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**).
7272

7373
```js
7474
function listenToWhatever() {
@@ -91,10 +91,8 @@ Hopefully, it's clear just how *dangerous* it is to have unpredictable program b
9191

9292
The only plausible solution here, while still taking advantage of *weak event listeners* capabiliity when it's actually helpful, is to ensure you only ever pass event listener functions that are stably and predictably referenced elsehwere in the program.
9393

94-
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.
94+
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.
9595

9696
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.
9797

98-
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.
99-
100-
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.
98+
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!

0 commit comments

Comments
 (0)