{"id":18870366,"url":"https://github.com/streetstrider/emitter","last_synced_at":"2026-05-01T17:34:34.635Z","repository":{"id":57155928,"uuid":"305510078","full_name":"StreetStrider/emitter","owner":"StreetStrider","description":"event emitter","archived":false,"fork":false,"pushed_at":"2023-08-19T13:33:17.000Z","size":33,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-04-25T15:44:31.839Z","etag":null,"topics":["browser","emitter","javascript","node"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/StreetStrider.png","metadata":{"files":{"readme":"readme.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"license.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-10-19T20:51:13.000Z","updated_at":"2021-11-04T12:20:49.000Z","dependencies_parsed_at":"2024-11-08T05:24:54.734Z","dependency_job_id":"820af75a-bc03-461b-93ef-e6ca4da57cc0","html_url":"https://github.com/StreetStrider/emitter","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StreetStrider%2Femitter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StreetStrider%2Femitter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StreetStrider%2Femitter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StreetStrider%2Femitter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/StreetStrider","download_url":"https://codeload.github.com/StreetStrider/emitter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239816505,"owners_count":19701753,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["browser","emitter","javascript","node"],"created_at":"2024-11-08T05:19:52.272Z","updated_at":"2026-05-01T17:34:34.619Z","avatar_url":"https://github.com/StreetStrider.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# emitter\n\n[![npm|@streetstrider/emitter](http://img.shields.io/badge/npm-@streetstrider/emitter-CB3837.svg?style=flat-square)](https://www.npmjs.org/package/@streetstrider/emitter)\n\n\u003e Emitter, MultiEmitter \u0026 Slot\n\nThis is a simple, easy, and robust implementation of the fundamental JS pattern called «Event Emitter». It is intended to be well-designed, free of misconceptions, and minimalistic. This library splits the pattern into **Emitter**, **MultiEmitter**, and **Slot**. Emitter can emit a single kind of event. MultiEmitter can emit several kinds of events. Slot can emit a single kind of event and guarantee to have no more than one subscription at a time.\n\n## [Features](#features) • [API](#api) • [Examples](#examples) • [Types](#types) • [Design](#some-design-solutions) • [License](#license)\n\n## Features\n* Single-channel emitter — no event name, just a list of subscriptions.\n* Multi-channel emitter (like EventEmitter or nanoevents).\n* Slot — single-channel emitter with single subscription.\n* Minimal overhead.\n* Composable API, clean separation of concerns.\n* Fast, optimized emit.\n* Minimal memory footprint, proper garbage collection.\n* Provides disposer.\n* Fully tested \u0026 100% coverage.\n* Precise TypeScript definitions; typedefs are well-tested.\n* The code is ES6 CJS with correct typedefs, works in any environment.\n* Emitter is 578 characters when minified (Multi is 971, Slot is 439, including strict and IIFE).\n\n## API\n\nYou can consult `.d.ts` files for the exact API. Below is pseudocode. Emitter and Slot have strong similarities, but Slot provides an additional `emit_must` method.\n\n```ts\ntype Disposer = () =\u003e void\ntype Subscription \u003cArgs extends unknown[]\u003e = (...args: Args) =\u003e void\n\ntype Emitter\u003cArgs extends unknown[]\u003e =\n{\n  on (fn: Subscription\u003cArgs\u003e): Disposer,\n  emit (...args: Args): void,\n  is_empty (): boolean,\n}\n```\n\n```ts\ntype Subscription \u003cArgs extends unknown[], Return = unknown\u003e = (...args: Args) =\u003e Return\n\ntype Slot\u003cArgs extends unknown[], Return = unknown\u003e =\n{\n  on (fn: Subscription\u003cArgs, Return\u003e): Disposer,\n  emit (...args: Args): Return,\n  emit_must (...args: Args): Return,\n  is_empty (): boolean,\n}\n```\n\n```ts\ntype Disposer = () =\u003e void\ntype Subscription \u003cArgs extends unknown[]\u003e = (...args: Args) =\u003e void\ntype HandlersBase =\n{\n  [key: string]: unknown[],\n}\n\ntype MultiEmitter \u003cHandlers extends HandlersBase\u003e =\n{\n  on \u003cKey extends keyof Handlers\u003e (key: Key, fn: Subscription\u003cHandlers[Key]\u003e): Disposer,\n  emit \u003cKey extends keyof Handlers\u003e (key: Key, ...args: Handlers[Key]): void,\n  is_empty (): boolean,\n}\n```\n\n## Examples\n```js\nimport Emitter from '@streetstrider/emitter'\nimport once from '@streetstrider/emitter/once'\n\nconst emitter = Emitter()\n\nconst disposer = emitter.on((a, b) =\u003e console.log(a + b))\nconst disposer = once(emitter, (a, b) =\u003e console.log(a + b))\n\nemitter.emit(1, 2)\n\ndisposer()\n\nemitter.is_empty() // → true\n```\n\n```js\nimport Slot from '@streetstrider/emitter/slot'\n\nconst slot = Slot()\n\nconst disposer = slot.on((a, b) =\u003e console.log(a + b))\n```\n\n```js\nimport MultiEmitter from '@streetstrider/emitter/multi'\nimport { multi as once_multi } from '@streetstrider/emitter/once'\n\nconst emitter = MultiEmitter()\n\nconst ds1 = emitter.on('plus', (a, b) =\u003e console.log(a + b))\nconst ds2 = emitter.on('mul',  (a, b) =\u003e console.log(a * b))\n\nconst ds3 = once_multi(emitter, 'plus', (a, b) =\u003e console.log(a + b))\nconst ds4 = once_multi(emitter, 'mul',  (a, b) =\u003e console.log(a * b))\n\nemitter.emit('plus', 1, 2)\nemitter.emit('mul', 3, 4)\n\nds1()\nds2()\n\nemitter.is_empty() // → true\n```\n\n```js\nimport Emitter from '@streetstrider/emitter'\nimport when from '@streetstrider/emitter/when'\n\nconst emitter = Emitter()\n\nasync function do_async () {\n  const next_one = await when(emitter) /* would capture first emission */\n}\n\nemitter.emit('next_one')\nemitter.emit('next_two')\nemitter.emit('next_three')\n```\n\n## Types\nBuilt-in TypeScript type definitions.\n```typescript\nconst e1 = Emitter\u003c[number, number]\u003e()\ne1.emit(1, 2)\n\nconst e2 = MultiEmitter\u003c{ plus: [number, number] }\u003e()\ne2.emit('plus', 1, 2)\n```\n\n## Some design solutions\n\n### Why Disposer instead of removeListener?\nDisposer is a simple `() =\u003e void` function that can be easily passed around, used as a one-off for an event from another emitter, and composed with other disposers via ordinary `compose`. You can pass it without wrapping it with an arrow function since disposer is a simple void function. It is much easier and cleaner than storing references to the original function and emitter. The disposer is exactly the same for Emitter, MultiEmitter, and Slot. Disposer make some efforts to disrupt references to prevent memory leaks and open a way for earlier garbage collection.\n\n### Why split Emitter and MultiEmitter?\nMost of the approaches (like EventEmitter or nanoevents) converge to the topmost powerful multi-channel emitter since it covers all the cases. However, in many situations, Emitter is just enough. It is much simpler and easier to have one or two separate Emitter instances with clean semantics rather than have some string-keyed channels inside MultiEmitter. It is not smart to use MultiEmitter if you have only one type of event.\n\n### Why split Emitter and Slot?\nIn many situations, the Event Emitter pattern is used for a singular subscription as a replacement for a callback. Slot still follows inversion of control like a normal Emitter. Instead of using a callback as an input parameter, we subscribe to a specific explicit Slot instance. A single subscription is guaranteed, so we have additional guarantees.\n\n### Why are `once` and `when` not in the core?\nBecause they are not part of the minimal API. Making them public methods on the emitter would also make them non-tree-shakeable, and the only gain would be consistent syntax with `on` (via dot). If you still want this, you can patch your emitters by binding/partialing (bring your own) `once` and/or `when`. You can also attach them by currying them.\n\n```js\nimport once from '@streetstrider/emitter/once'\n\nconst emitter = Emitter()\n\nemitter.once = once.bind(null, emitter)\nemitter.once = partial(once, emitter)\n\nconst disposer = emitter.once((a, b) =\u003e console.log(a + b))\n```\n\n```js\nimport { multi as once_multi } from '@streetstrider/emitter/once'\n\nconst emitter = MultiEmitter()\n\nemitter.once = once_multi.bind(null, emitter)\nemitter.once = partial(once_multi, emitter)\n\nconst disposer = emitter.once('plus', (a, b) =\u003e console.log(a + b))\n```\n\n## License\nISC, © Strider, 2026.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstreetstrider%2Femitter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstreetstrider%2Femitter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstreetstrider%2Femitter/lists"}