{"_id":"@byojs/eventer","_rev":"4-647b79801b7aeda54d48ca6571e1d9d6","name":"@byojs/eventer","dist-tags":{"latest":"0.1.2"},"versions":{"0.0.1":{"name":"@byojs/eventer","version":"0.0.1","keywords":["events","emitter","async","pubsub"],"author":{"name":"Kyle Simpson","email":"getify@gmail.com"},"license":"MIT","_id":"@byojs/eventer@0.0.1","maintainers":[{"name":"getify","email":"getify@gmail.com"}],"homepage":"https://github.com/byojs/eventer","bugs":{"url":"https://github.com/byojs/eventer/issues","email":"getify@gmail.com"},"dist":{"shasum":"c3bfa16d563a629e358ede02325a9f0f7ace7c46","tarball":"https://registry.npmjs.org/@byojs/eventer/-/eventer-0.0.1.tgz","fileCount":12,"integrity":"sha512-K3Q8Z5TeDJwebANBsFR2lhu7zPhoHolRltBQzVDCFPABTUuPbTJItQ7bdHoEi6AxqGNCfRU2bGDo6SPMdNHALA==","signatures":[{"sig":"MEUCIQCpsFivHmkpKSy7XkRyzJCQR1MDV4ePX4azEaLP9OGfRgIgbjDLLjv8xf3oLDukzV22Sm6QStZ8WYM40m73IhUKO2g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":64999},"browser":{"@byojs/eventer":"./dist/eventer.mjs"},"exports":{"./":"./dist/eventer.mjs"},"gitHead":"f951c938e08b88462fa907c702a8b76e49a7e3fe","scripts":{"test":"npm run test:start","build":"npm run build:all","build:all":"node scripts/build-all.js","test:start":"npx http-server test/ -p 8080","postinstall":"node scripts/postinstall.js","build:gh-pages":"npm run build:all && node scripts/build-gh-pages.js","prepublishOnly":"npm run build:all"},"_npmUser":{"name":"getify","email":"getify@gmail.com"},"repository":{"url":"git+https://github.com/byojs/eventer.git","type":"git"},"_npmVersion":"10.8.2","description":"Event emitter with async-emit and weak-listener support","directories":{},"_nodeVersion":"21.7.2","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"terser":"~5.31.6","micromatch":"~4.0.8","recursive-readdir-sync":"~1.0.6"},"_npmOperationalInternal":{"tmp":"tmp/eventer_0.0.1_1726071488171_0.20689195872446176","host":"s3://npm-registry-packages"}},"0.0.2":{"name":"@byojs/eventer","version":"0.0.2","keywords":["events","emitter","async","pubsub","weak references","memory"],"author":{"name":"Kyle Simpson","email":"getify@gmail.com"},"license":"MIT","_id":"@byojs/eventer@0.0.2","maintainers":[{"name":"getify","email":"getify@gmail.com"}],"homepage":"https://github.com/byojs/eventer","bugs":{"url":"https://github.com/byojs/eventer/issues","email":"getify@gmail.com"},"dist":{"shasum":"814ab61109aa3848810778fd91bc1018d790843f","tarball":"https://registry.npmjs.org/@byojs/eventer/-/eventer-0.0.2.tgz","fileCount":12,"integrity":"sha512-rH8mk9vdNovzA2NwAykkMhYXmPCjVBQ7OGQZbqS4CBDSGAKAsTJK1ktYph4gHG1ShbZb6jJl4+NtNmpVNe3JtQ==","signatures":[{"sig":"MEQCIHf1wlLRuLM4qhmfJBuqxKrP4oxrFTxbL3UpscRUjRcpAiAi2SSUNJ/ko+TnHblIWpnpUBwK8Vy/dXrra89cAtNCaw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":65044},"browser":{"@byojs/eventer":"./dist/eventer.mjs"},"exports":{"./":"./dist/eventer.mjs"},"gitHead":"fe44ce69bfb8c4404162cd437f69b7fd97ece22c","scripts":{"test":"npm run test:start","build":"npm run build:all","build:all":"node scripts/build-all.js","test:start":"npx http-server test/ -p 8080","postinstall":"node scripts/postinstall.js","build:gh-pages":"npm run build:all && node scripts/build-gh-pages.js","prepublishOnly":"npm run build:all"},"_npmUser":{"name":"getify","email":"getify@gmail.com"},"repository":{"url":"git+https://github.com/byojs/eventer.git","type":"git"},"_npmVersion":"10.8.2","description":"Event emitter with async-emit and weak-listener support","directories":{},"_nodeVersion":"21.7.2","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"terser":"~5.31.6","micromatch":"~4.0.8","recursive-readdir-sync":"~1.0.6"},"_npmOperationalInternal":{"tmp":"tmp/eventer_0.0.2_1726361044416_0.5651886064305596","host":"s3://npm-registry-packages"}},"0.1.0":{"name":"@byojs/eventer","version":"0.1.0","keywords":["events","emitter","async","pubsub","weak references","memory"],"author":{"name":"Kyle Simpson","email":"getify@gmail.com"},"license":"MIT","_id":"@byojs/eventer@0.1.0","maintainers":[{"name":"getify","email":"getify@gmail.com"}],"homepage":"https://github.com/byojs/eventer","bugs":{"url":"https://github.com/byojs/eventer/issues","email":"getify@gmail.com"},"dist":{"shasum":"97c087bc22a8b2c15d7aaf6212b2eb1652ab633d","tarball":"https://registry.npmjs.org/@byojs/eventer/-/eventer-0.1.0.tgz","fileCount":12,"integrity":"sha512-k95CbqkAKlUjqvqKMChwvuGmnM9DZ73SgaTEaxUgZ8ZG1WZwQXPuS8cV9dy5ZFje+tgKIT1BESkuOqunZF1oJA==","signatures":[{"sig":"MEUCIQDQl8njqmFXg37mZ5GYr3d8292c62MP52TBW2UshKfYygIgVN04k27i7KpAnlYDgRmRSZFv9i6pG4ZnUUH64KF3Vkk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":74282},"browser":{"@byojs/eventer":"./dist/eventer.mjs"},"exports":{"./":"./dist/eventer.mjs"},"gitHead":"6dadc0a46bcaa8a85dc313f7f81f747507b6de16","scripts":{"test":"npm run test:start","build":"npm run build:all","build:all":"node scripts/build-all.js","test:start":"npx http-server test/ -p 8080","postinstall":"node scripts/postinstall.js","build:gh-pages":"npm run build:all && node scripts/build-gh-pages.js","prepublishOnly":"npm run build:all"},"_npmUser":{"name":"getify","email":"getify@gmail.com"},"repository":{"url":"git+https://github.com/byojs/eventer.git","type":"git"},"_npmVersion":"10.8.2","description":"Event emitter with async-emit and weak-listener support","directories":{},"_nodeVersion":"21.7.2","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"terser":"~5.31.6","micromatch":"~4.0.8","recursive-readdir-sync":"~1.0.6"},"_npmOperationalInternal":{"tmp":"tmp/eventer_0.1.0_1727315768738_0.11744919710017032","host":"s3://npm-registry-packages"}},"0.1.1":{"name":"@byojs/eventer","version":"0.1.1","keywords":["events","emitter","async","pubsub","weak references","memory"],"author":{"name":"Kyle Simpson","email":"getify@gmail.com"},"license":"MIT","_id":"@byojs/eventer@0.1.1","maintainers":[{"name":"getify","email":"getify@gmail.com"}],"homepage":"https://github.com/byojs/eventer","bugs":{"url":"https://github.com/byojs/eventer/issues","email":"getify@gmail.com"},"dist":{"shasum":"6c125a4596bbac2aa779649ef394792481ca13a6","tarball":"https://registry.npmjs.org/@byojs/eventer/-/eventer-0.1.1.tgz","fileCount":12,"integrity":"sha512-6uIDFm4f912f9hA7IYlnTXOFM/LcrC7nHh0Cbn/TvIoOi2sK4YGnXgcn5gEnAHPb2qrSbiwjzgLsPz82iDb2JA==","signatures":[{"sig":"MEUCIQCd/1VVxIan6eiP8DZZpQRM8SZQ3MV/icIgbf4YpRFTKQIgALXhwai+3nzI1rzWIbHKxizyIYLfsp3GHypMTGssTiU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":76064},"browser":{"@byojs/eventer":"./dist/eventer.mjs"},"exports":{"./":"./dist/eventer.mjs"},"gitHead":"17894f3798e413b7a7ff829e8df6838b4425f16d","scripts":{"test":"npm run test:start","build":"npm run build:all","build:all":"node scripts/build-all.js","test:start":"npx http-server test/ -p 8080","postinstall":"node scripts/postinstall.js","build:gh-pages":"npm run build:all && node scripts/build-gh-pages.js","prepublishOnly":"npm run build:all"},"_npmUser":{"name":"getify","email":"getify@gmail.com"},"repository":{"url":"git+https://github.com/byojs/eventer.git","type":"git"},"_npmVersion":"10.8.2","description":"Event emitter with async-emit and weak-listener support","directories":{},"_nodeVersion":"21.7.2","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"terser":"~5.31.6","micromatch":"~4.0.8","recursive-readdir-sync":"~1.0.6"},"_npmOperationalInternal":{"tmp":"tmp/eventer_0.1.1_1727362167742_0.12327680441722655","host":"s3://npm-registry-packages"}},"0.1.2":{"name":"@byojs/eventer","description":"Event emitter with optional async-emit and weak-listener support","version":"0.1.2","exports":{"./":"./dist/eventer.mjs"},"browser":{"@byojs/eventer":"./dist/eventer.mjs"},"scripts":{"build:all":"node scripts/build-all.js","build:gh-pages":"npm run build:all && node scripts/build-gh-pages.js","build":"npm run build:all","test:start":"npx http-server test/ -p 8080","test":"npm run test:start","postinstall":"node scripts/postinstall.js","prepublishOnly":"npm run build:all"},"dependencies":{},"devDependencies":{"micromatch":"~4.0.8","recursive-readdir-sync":"~1.0.6","terser":"~5.37.0"},"repository":{"type":"git","url":"git+https://github.com/byojs/eventer.git"},"keywords":["events","emitter","async","pubsub","weak references","memory"],"bugs":{"url":"https://github.com/byojs/eventer/issues","email":"getify@gmail.com"},"homepage":"https://github.com/byojs/eventer","author":{"name":"Kyle Simpson","email":"getify@gmail.com"},"license":"MIT","_id":"@byojs/eventer@0.1.2","gitHead":"c39a834ba0d19816aea53b7c6af42b8edceeb8f2","_nodeVersion":"21.7.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-7nDY1NA3Uho+LK/m9DsrquQMINGqy/PoV5uk6XddaVT4i0cuB3Uq0Fem9uWrpIjGlxeNw3c235pNWcYXqesX8w==","shasum":"606c76721a8d52f68b5c78dc16911b8e98f51417","tarball":"https://registry.npmjs.org/@byojs/eventer/-/eventer-0.1.2.tgz","fileCount":12,"unpackedSize":76073,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQC8/6GCNGGxhnUfxAuZSDp0fGKLdhB5E1RfZma7hPgvTAIgcX0qvzBe6jw99bNJ8LQglbJ5XMDLXvaDIa1AfKlHdbA="}]},"_npmUser":{"name":"getify","email":"getify@gmail.com"},"directories":{},"maintainers":[{"name":"getify","email":"getify@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/eventer_0.1.2_1738709414920_0.7337732048482772"},"_hasShrinkwrap":false}},"time":{"created":"2024-09-11T16:18:08.044Z","modified":"2025-02-04T22:50:15.311Z","0.0.1":"2024-09-11T16:18:08.302Z","0.0.2":"2024-09-15T00:44:04.549Z","0.1.0":"2024-09-26T01:56:08.974Z","0.1.1":"2024-09-26T14:49:28.023Z","0.1.2":"2025-02-04T22:50:15.140Z"},"bugs":{"url":"https://github.com/byojs/eventer/issues","email":"getify@gmail.com"},"author":{"name":"Kyle Simpson","email":"getify@gmail.com"},"license":"MIT","homepage":"https://github.com/byojs/eventer","keywords":["events","emitter","async","pubsub","weak references","memory"],"repository":{"type":"git","url":"git+https://github.com/byojs/eventer.git"},"description":"Event emitter with optional async-emit and weak-listener support","maintainers":[{"name":"getify","email":"getify@gmail.com"}],"readme":"# Eventer\n\n[![npm Module](https://badge.fury.io/js/@byojs%2Feventer.svg)](https://www.npmjs.org/package/@byojs/eventer)\n[![License](https://img.shields.io/badge/license-MIT-a1356a)](LICENSE.txt)\n\n**Eventer** is a zero-dependency event emitter, with optional support for async `emit()`, and [weak event listeners](WEAK.md).\n\n```js\nconst onUpdate = data => {\n    console.log(`Data updated: ${data}`);\n};\n\nevents.on(\"update\",onUpdate);\n\nevents.emit(\"update\",{ hello: \"world\" })\n// Data updated: { hello: \"world\" }\n```\n\n----\n\n[Library Tests (Demo)](https://byojs.dev/eventer/)\n\n----\n\n## Overview\n\nThe 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:\n\n1. async `emit()`: asynchronous event handling sometimes makes it easier to work around difficult issues with event handling.\n\n    For example, if the listener for one event subscribes or unsubscribes other event handlers, you can run into events that fire when they shouldn't (or vice versa). Or you may encounter infinite event loops (events calling each other mutually, for ever).\n\n    On the other hand, asynchrony is always more intricate to manage propperly. Developers should use caution when deciding how to handle events.\n\n    **Eventer** supports both *sync* and *async* modes for event emission; this mode is configured at emitter instance creation instead of at every `emit()` call.\n\n2. [weak event listeners](WEAK.md): this is a pattern for managing the subscription of events, which holds a reference to the listener (function) *weakly*; 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).\n\n    Typically, developers have to remember to remove an event subscription if the listener (or any object it belongs to) is intentionally being unset for GC purposes; otherwise, an event emitter's default *strong reference* keeps that listener value (and its closure!) alive, preventing GC.\n\n    **Eventer** supports both *strong* and *weak* modes for listener subscription; this mode is configured at emitter instance creation instead of every `on()` / `once()` call.\n\n## Deployment / Import\n\n```cmd\nnpm install @byojs/eventer\n```\n\nThe [**@byojs/eventer** npm package](https://npmjs.com/package/@byojs/eventer) includes a `dist/` directory with all files you need to deploy **Eventer** (and its dependencies) into your application/project.\n\n**Note:** If you obtain this library via git instead of npm, you'll need to [build `dist/` manually](#re-building-dist) before deployment.\n\n### Using a bundler\n\nIf you are using a bundler (Astro, Vite, Webpack, etc) for your web application, you should not need to manually copy any files from `dist/`.\n\nJust `import` like so:\n\n```js\nimport Eventer from \"@byojs/eventer\";\n```\n\nThe bundler tool should pick up and find whatever files (and dependencies) are needed.\n\n### Without using a bundler\n\nIf you are not using a bundler (Astro, Vite, Webpack, etc) for your web application, and just deploying the contents of `dist/` as-is without changes (e.g., to `/path/to/js-assets/eventer/`), you'll need an [Import Map](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) in your app's HTML:\n\n```html\n<script type=\"importmap\">\n{\n    \"imports\": {\n        \"eventer\": \"/path/to/js-assets/eventer.mjs\"\n    }\n}\n</script>\n```\n\nNow, you'll be able to `import` the library in your app in a friendly/readable way:\n\n```js\nimport Eventer from \"eventer\";\n```\n\n**Note:** If you omit the above *eventer* import-map entry, you can still `import` **Eventer** by specifying the proper full path to the `eventer.mjs` file.\n\n## Eventer API\n\nThe API provided by **Eventer** is a single constructor function, to create emitter instances:\n\n```js\nimport Eventer from \"..\";\n\nvar events = new Eventer({ /* options */ });\n```\n\nThe options that can be passed to the constructor:\n\n* `asyncEmit` (default: `false`): controls whether `emit()` calls will immediately trigger event listeners, or wait for the next asynchronous microtask to trigger them.\n\n* `weakListeners` (default: `true`): controls whether any listeners (function callbacks) are held *strongly* (as typical) or [*weakly* (for more advanced memory management)](WEAK.md).\n\n### Class-based composition\n\nThe exposed API function (`Eventer()` above) can act as a constructable `class` (as seen with the `new` call), which means it can be used in an `extends` clause of a child/derived class:\n\n```js\nclass myGreatStuff extends Eventer {\n    constructor(eventerOptions,otherOptions) {\n        super(eventerOptions);\n        // ..\n    }\n\n    // ..\n}\n\nvar thing = new myGreatStuff(..);\n\nthing instanceof MyGreatStuff;  // true\nthing instanceof Eventer;       // true\n\nthing.emit(\"whatever\");\n```\n\n*Composition through inheritance* essentially *mixes in* event emitter capabilities to your own data structure definition. Many prefer this approach.\n\nOthers prefer a more explicit form of *composition* (over/instead of inheritance) to maintain an **Eventer** instance as a clean, separate object. For example:\n\n```js\nclass myGreatStuff {\n    eventer = new Eventer(..)\n\n    // ..\n}\n\nvar thing = new myGreatStuff(..);\n\nthing.eventer.emit(\"whatever\");\n```\n\n### Without classes\n\nIf you're not using `Eventer()` as an inheritable parent class, you don't really have to use `class` design at all.\n\nIn fact, `Eventer()` can be called as a *factory function* without `new`, if you prefer:\n\n```js\nvar events = Eventer({ /* options */ });\n```\n\n### Be aware of `this`!\n\nEven if `Eventer()` is called without `new`, a class instance is still created underneath. That means that the methods on the returned object instance (e.g., `events.emit(..)`) are `this`-aware of their host context (instance).\n\nThe following approaches *will break*:\n\n```js\nvar myEmit = events.emit;\n\nmyEmit(\"whatever\");         // broken!\n```\n\n```js\nsomeAsyncTask().then(events.emit);  // broken!\n```\n\n```js\nevents.emit.call(myOtherObject,\"whatever\");  // broken!\n```\n\nInstead, you'll need to ensure methods are always called against their original instance as `this` context.\n\n```js\nevents.emit(\"whatever\");  // safe, preferred\n```\n\n```js\nvar myEmit = events.emit;\n\nmyEmit.call(events,\"whatever\");  // safe\n```\n\n```js\nsomeAsyncTask().then(\n    evtName => events.emit(evtName)  // safe, preferred\n);\n\nsomeAsyncTask().then(\n    events.emit.bind(events)  // safe\n);\n```\n\n## Instance API\n\nEach instance of **Eventer** provides the following methods.\n\n### `on(..)` Method\n\nThe `on(..)` method subscribes a listener (function) to an event (by string name, or `Symbol` value):\n\n```js\nfunction onWhatever() {\n    console.log(\"'whatever' event fired!\");\n}\n\n// subscribe to \"whatever\" event\nevents.on(\"whatever\",onWhatever);\n```\n\n```js\nfunction onSpecialEvent() {\n    console.log(\"special event fired!\");\n}\n\n// subscribe to `specialEvent` event\nvar specialEvent = Symbol(\"special event\");\nevents.on(specialEvent,onSpecialEvent);\n```\n\nEvent 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!\n\nEvent 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.\n\nThe `on(..)` method returns `true` if successfully subscribed, or `false` (if subscription was skipped).\n\n### Event arguments\n\nAn event listener function may optionally declare one or more parameters, which are passed in as arguments when the event is [`emit(..)`ed](#emit-method).\n\nFor example:\n\n```js\nfunction onPositionUpdate(x,y) {\n    console.log(`Map position: (${x},${y})`);\n}\n\nmyMap.on(\"position-update\",onPositionUpdate);\n\n// elsewhere:\nmyMap.emit(\"position-update\",centerX,centerY);\n```\n\n### `AbortSignal` unsubscription\n\nA 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.\n\n**Eventer** also supports this functionality:\n\n```js\nfunction onWhatever() {\n    console.log(\"'whatever' event fired!\");\n}\n\nvar ac = new AbortController();\n\n// subscribe to \"whatever\" event, but set up\n// the abort-signal to unsubscribe\nevents.on(\"whatever\",onWhatever,{ signal: ac.signal });\n\n// later:\nac.abort(\"Unsubscribe!\");\n```\n\n**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.\n\n### Inline event listeners (functions)\n\nIt'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**.\n\n#### NOT inline event listeners\n\nBefore we explain those gotchas, let's highlight the preferred alternatives to inline functions (as already implied in previous snippets!):\n\n```js\nfunction onWhatever() {\n    // this is a safe and stable event listener\n    console.log(\"'whatever' event fired!\");\n}\n\nevents.on(\"whatever\",onWhatever);\n```\n\n```js\nvar myApp = {\n    // ..\n    onWhatever() {\n        // this is a safe and stable event listener,\n        // as long as it's not `this`-dependent\n        console.log(\"'whatever' event fired!\");\n    }\n    // ..\n};\n\nevents.on(\"whatever\",myApp.onWhatever);\n```\n\n```js\nclass App {\n    // ..\n    onWhatever = () => {\n        // this is a safe and stable event listener,\n        // even if it uses `this` (since it's a\n        // lexical-`this` arrow function)\n        console.log(\"'whatever' event fired!\");\n    }\n    // ..\n}\n\nvar myApp = new App();\n\nevents.on(\"whatever\",myApp.onWhatever);\n```\n\nAll of these approaches are *safe* and avoid the issues we will now cover with using inline function listeners.\n\n#### Inline handler gotchas\n\nFirst of all, the subscription (`on(..)` / `once(..)`) mechanism uses function reference identity to determine uniqueness of event+listener subscription. If you pass an inline function expression (or a dynamically `this`-bound function instance), each subscription will use a new function; the duplicate-subscription prevention will be defeated, potentially leading to bugs.\n\nFor example:\n\n```js\nfunction listenToWhatever() {\n    events.on(\n        \"whatever\",\n        () => console.log(\"'whatever' event fired!\")\n    );\n}\n\nlistenToWhatever();\n\n// later, elsewhere:\nlistenToWhatever();\n```\n\nHere, each `=>` arrow function is unique (per `listenToWhatever()` call), so there are now two distinct event subscriptions. When the `\"whatever\"` event is fired, *both* listeners will fire. This may be desired, but it's often a confusing gotcha bug.\n\nIt's generally a good idea to pass non-inline functions (with stable definitions), as listeners; this enables **Eventer**'s helpful duplicate event handler prevention.\n\n#### Unsubscribe what?\n\nAnother concern with passing inline functions as listeners: the most common/preferred [`off(..)` unsubscription approach](#off-method) requires the same function reference for unsubscription as was originally subscribed. You almost certainly will not hold another reference to an inline function -- by definition, it was defined only *inline* at the subscription site -- to use in its later unsubscription.\n\n```js\nevents.on(\n    \"whatever\",\n    () => console.log(\"'whatever' event fired!\")\n);\n\n// later:\nevents.off(\"whatever\", /* OOPS, what do I pass here!? */)\n```\n\n**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).\n\n#### Accidental unsubscription\n\nThe 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.\n\nSince 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.\n\nBy 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.\n\nHowever, GC is *inherently and intentionally* somewhat unpredictable. It's not guaranteed, or even likely, that GC will happen immediately on a lexical scope being completed; it *may* happen sometime in the near future -- and, only if there are no intentional or accidental closures keeping all or part of the lexical scope alive!\n\nThat means your event subscriptions with inline functions **are subject to fairly unpredictable behavior**. They may fire for awhile and then silently stop, even with no further affirmative action from your controlling app code.\n\nFor illustration:\n\n```js\nfunction listenToWhatever() {\n    events.on(\n        \"whatever\",\n        () => console.log(\"'whatever' event fired!\")\n    );\n}\n\nlistenToWhatever();\n```\n\nAfter 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!\n\nHopefully it's clear that you should avoid inline function listeners, at least when using the *weak event listeners* mode of **Eventer**.\n\n### `once(..)` Method\n\nThe `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\".\n\n```js\nfunction onWhateverOnce() {\n    console.log(\"'whatever' event fired (just once)!\");\n}\n\n// subscribe to \"whatever\" event, but only once!\nevents.once(\"whatever\",onWhateverOnce);\n```\n\n`once(..)` and `on(..)` perform the same kind of event subscription. Subsequent calls of `once(..)` or `on(..)` (in any combination) will skip any subsequent subscriptions (returning `false`). You cannot *switch* from `on(..)` to `once(..)` style subscription (or vice versa) by calling one method after the other (with the same event+listener); to switch, you must first [unsubscribe with `off(..)`](#off-method) before re-subscribing.\n\n### `off(..)` Method\n\nThe `off(..)` method unsubscribes an event+listener that was previously subscribed with the [`on(..)`](#on-method) or [`once(..)`](#once-method) methods.\n\n```js\nfunction onWhatever() {\n    console.log(\"'whatever' event fired!\");\n}\n\n// unsubscribe from \"whatever\" event\nevents.off(\"whatever\",onWhatever);\n```\n\nThe method will return `true` if the event was unsubscribed, or `false` if no matching event+listener subscription could be found.\n\n#### Alternate unsubscription\n\nThe two arguments to `off(..)` are *both optional*.\n\nIf you pass only the first *event-name* argument, but leave off the listener argument, all liseners for that event will be removed:\n\n```js\n// remove any 'whatever' listeners\nevents.off(\"whatever\");\n```\n\n`true` will be returned if any event listeners are currently subscribed, or `false` otherwise.\n\nIf you instead pass only the second *listener* argument (with `null` or `undefined` for the first *event-name* argument), it will unsubscribe *all events* that have included that specific listener:\n\n```js\nfunction onEvent() { /* .. */ }\n\nevents.off(null,onEvent);\n```\n\n`true` will be returned if the listener is subscribed to any events, or `false` otherwise.\n\nIf you call `off()` with no arguments, *all events* with *any event listeners* are unsubscribed:\n\n```js\n// clear out all event subscriptions unconditionally!\nevents.off();\n```\n\n`true` will be returned if any event+listener subscription is found to remove, or `false` otherwise.\n\n### `emit(..)` Method\n\nTo *emit* an event against all listeners on an emitter instance, call `emit(..)`:\n\n```js\nevents.emit(\"whatever\");\n```\n\n```js\n// specialEvent: Symbol(\"special event\")\n\nevents.emit(specialEvent);\n```\n\n**Note:** If a listener function throws an exception, this error will be reported to the consolve (via `console.error()`), but will not stop the `emit()` call. All handlers will be given a fair chance to execute.\n\nAny subscription/unsubscription operations *from/during a listener execution* will NOT take effect until after all event listeners queued by `emit()` have had a chance to be invoked ([synchronously or asynchronously](#sync-vs-async-modes)).\n\nYou can optionally pass one or more arguments after the event name, which will be passed to the event listener(s):\n\n```js\nevents.emit(\"whatever\",42,[ \"hello\", \"world\" ]);\n```\n\n**Note:** This call will pass *two* arguments to the listener function(s), `42` and the array `[\"hello\",\"world\"]`.\n\n#### Sync vs Async modes\n\nIf the emitter is in *sync-emit* mode (default, [configured at instance construction](#eventer-api)), any matching listener function(s) will be called synchronously during the `emit(..)` call.\n\n```js\nfunction onWhatever() {\n    console.log(\"'whatever' event fired!\");\n}\n\nvar events = new Eventer({ asyncEmit: false });\n\nevents.on(\"whatever\",onWhatever);\nevents.emit(\"whatever\");\nconsole.log(\"Done.\");\n// 'whatever' event fired!\n// Done.\n```\n\n**Note:** The `emit()` call in *sync mode* invokes all event listeners while it is running, which is why `Done.` message is printed last.\n\nIf the emitter is in *async-emit* mode ([configured at instance construction](#eventer-api)), any matching listener function(s) **at the time of `emit()` call** will be asynchronously scheduled for the next microtask. However, `emit()` always still completes immediately.\n\n```js\nfunction onWhatever() {\n    console.log(\"'whatever' event fired!\");\n}\n\nvar events = new Eventer({ asyncEmit: true });\n\nevents.on(\"whatever\",onWhatever);\nevents.emit(\"whatever\");\nconsole.log(\"Done.\");\n// Done.\n// 'whatever' event fired!\n```\n\n**Note:** Here (async mode), the `Done.` message is printed first, because the current stack of execution completes before the next microtask runs (and processes async scheduled event listener invocations).\n\n### `releaseListeners(..)` Method\n\nIf using [*weak event listeners* mode](WEAK.md) (default), the `releaseListeners(..)` method is no-op (does nothing).\n\nBut if that mode is turned off (i.e., *strong event listeners* mode), the `releaseListeners(..)` mode can be used to release a specific listener, or all listeners if no argument is passed.\n\n```js\n// release specific event listener\nevents.releaseListeners(onWhatever);\n```\n\n```js\n// release all event listeners\nevents.releaseListeners();\n```\n\nThis method is intended for use as proactive-cleanup, under the specific circumstance when you know that the subscribed listener(s) in question *will go out of scope* (and otherwise be GC'd) in the future, and you want the event(s)+listener(s) to be implicitly unsubscribed when doing so.\n\nIn other words, it's a way to opt-in to [*weak event listeners* mode](WEAK.md), on an otherwise *strong event listeners* mode emitter instance, but **only for currently subscribed listeners** (not future subscriptions on the instance).\n\nThis 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.\n\n**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.\n\n## Re-building `dist/*`\n\nIf you need to rebuild the `dist/*` files for any reason, run:\n\n```cmd\n# only needed one time\nnpm install\n\nnpm run build:all\n```\n\n## Tests\n\nThis library only works in a browser, so its test suite must also be run in a browser.\n\nVisit [`https://byojs.dev/eventer/`](https://byojs.dev/eventer/) and click the \"run tests\" button.\n\n### Run Locally\n\nTo instead run the tests locally, first make sure you've [already run the build](#re-building-dist), then:\n\n```cmd\nnpm test\n```\n\nThis will start a static file webserver (no server logic), serving the interactive test page from `http://localhost:8080/`; visit this page in your browser and click the \"run tests\" button.\n\nBy default, the `test/test.js` file imports the code from the `src/*` directly. However, to test against the `dist/*` files (as included in the npm package), you can modify `test/test.js`, updating the `/src` in its `import` statements to `/dist` (see the import-map in `test/index.html` for more details).\n\n## License\n\n[![License](https://img.shields.io/badge/license-MIT-a1356a)](LICENSE.txt)\n\nAll code and documentation are (c) 2024 Kyle Simpson and released under the [MIT License](http://getify.mit-license.org/). A copy of the MIT License [is also included](LICENSE.txt).\n","readmeFilename":"README.md"}