├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── __tests__ ├── emit.ts ├── eventNames.ts ├── listenerCount.ts ├── listeners.ts ├── on.ts ├── once.ts ├── prependListener.ts ├── prependOnceListener.ts ├── removeAllListeners.ts ├── removeListener.ts └── symbols.ts ├── _config.yml ├── benchmarks ├── README.md ├── add-remove.js ├── context.js ├── emit-multiple-listeners.js ├── emit.js ├── hundreds.js ├── init.js ├── listeners.js ├── once.js ├── package.json └── start.sh ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── EventEmitter.ts ├── Listener.ts └── index.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended" 10 | ], 11 | "parser": "@typescript-eslint/parser", 12 | "parserOptions": { 13 | "ecmaVersion": 12, 14 | "sourceType": "module" 15 | }, 16 | "plugins": [ 17 | "@typescript-eslint", 18 | "jest" 19 | ], 20 | "rules": { 21 | "indent": [ 22 | "error", 23 | 2 24 | ], 25 | "quotes": [ 26 | "error", 27 | "double" 28 | ], 29 | "comma-dangle": [ 30 | "error", 31 | "always-multiline" 32 | ], 33 | "@typescript-eslint/comma-dangle": [ 34 | "error", 35 | "always-multiline" 36 | ], 37 | "semi": [ 38 | "error", 39 | "always" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: 'Bug: [BUG]' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Environment (please complete the following information):** 27 | - OS: [e.g. `MacOS`] 28 | - Node.js version: [e.g. `16.13.2`] 29 | - TypeScript version: [e.g. `4.5.4`] 30 | - Package Version: [e.g. `1.0.0`] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | test: 10 | name: Test 11 | 12 | runs-on: ubuntu-latest 13 | 14 | env: 15 | RELEASE: ${{ github.ref }} 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Use Node.js 16 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: 16 24 | 25 | - name: Install packages 26 | run: npm ci 27 | 28 | - name: Lint 29 | run: npm run lint 30 | 31 | - name: Build 32 | run: npm run build 33 | 34 | - name: Test 35 | run: npm run test:ci 36 | 37 | - name: Upload coverage to Codecov 38 | uses: codecov/codecov-action@v1 39 | with: 40 | env_vars: RELEASE 41 | fail_ci_if_error: true 42 | 43 | release: 44 | name: Create Release 45 | 46 | runs-on: ubuntu-latest 47 | 48 | needs: 49 | - test 50 | 51 | steps: 52 | - uses: actions/checkout@v2 53 | 54 | - name: Changelog 55 | id: changelog 56 | uses: ardalanamini/auto-changelog@v1.2.0 57 | with: 58 | token: ${{ secrets.GITHUB_TOKEN }} 59 | 60 | - name: Create Release 61 | uses: softprops/action-gh-release@v1 62 | env: 63 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 64 | with: 65 | body: | 66 | ${{ steps.changelog.outputs.changelog }} 67 | 68 | publish-npm: 69 | name: Publish to NPM 70 | 71 | runs-on: ubuntu-latest 72 | 73 | needs: 74 | - release 75 | 76 | steps: 77 | - uses: actions/checkout@v2 78 | 79 | - name: Use Node.js 16 80 | uses: actions/setup-node@v1 81 | with: 82 | node-version: 16 83 | registry-url: https://registry.npmjs.org/ 84 | 85 | - name: Install packages 86 | run: npm ci 87 | 88 | - name: Build 89 | run: npm run build 90 | 91 | - name: Publish 92 | run: npm publish --access public 93 | env: 94 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 95 | 96 | publish-gpr: 97 | name: Publish to Github 98 | 99 | runs-on: ubuntu-latest 100 | 101 | needs: 102 | - release 103 | 104 | steps: 105 | - uses: actions/checkout@v2 106 | 107 | - name: Use Node.js 16 108 | uses: actions/setup-node@v1 109 | with: 110 | node-version: 16 111 | registry-url: https://npm.pkg.github.com/ 112 | scope: '@foxifyjs' 113 | 114 | - run: sed -i 's/@foxify\/events/@foxifyjs\/events/g' package.json package-lock.json 115 | 116 | - name: Install packages 117 | run: npm ci 118 | 119 | - name: Build 120 | run: npm run build 121 | 122 | - name: Publish 123 | run: npm publish 124 | env: 125 | NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 126 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Test 5 | 6 | on: 7 | schedule: 8 | - cron: "0 0 * * *" 9 | push: 10 | branches: 11 | - master 12 | pull_request: 13 | 14 | jobs: 15 | test: 16 | 17 | strategy: 18 | matrix: 19 | os: 20 | - macos-latest 21 | - ubuntu-latest 22 | - windows-latest 23 | node-version: 24 | - 14 25 | - 15 26 | - 16 27 | - 17 28 | 29 | runs-on: ${{ matrix.os }} 30 | 31 | env: 32 | OS: ${{ matrix.os }} 33 | NODE_VERSION: ${{ matrix.node-version }} 34 | 35 | steps: 36 | - uses: actions/checkout@v2 37 | 38 | - name: Setup Cache 39 | uses: actions/cache@v2 40 | with: 41 | path: '**/node_modules' 42 | key: ${{ matrix.os }}-${{ matrix.node-version }}-modules-${{ hashFiles('**/package-lock.json') }} 43 | 44 | - name: Use Node.js ${{ matrix.node-version }} 45 | uses: actions/setup-node@v1 46 | with: 47 | node-version: ${{ matrix.node-version }} 48 | 49 | - name: Install packages 50 | run: npm i 51 | 52 | - name: Lint 53 | run: npm run lint 54 | 55 | - name: Build 56 | run: npm run build 57 | 58 | - name: Test 59 | run: npm run test:ci 60 | 61 | - name: Benchmark 62 | if: github.event_name == 'schedule' 63 | run: npm run benchmarks 64 | 65 | - name: Upload coverage to Codecov 66 | if: github.event_name != 'schedule' 67 | uses: codecov/codecov-action@v1 68 | with: 69 | env_vars: OS,NODE_VERSION 70 | fail_ci_if_error: true 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (https://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # TypeScript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | .env.test 64 | 65 | # parcel-bundler cache (https://parceljs.org/) 66 | .cache 67 | 68 | # next.js build output 69 | .next 70 | 71 | # nuxt.js build output 72 | .nuxt 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless/ 79 | 80 | # FuseBox cache 81 | .fusebox/ 82 | 83 | # DynamoDB Local files 84 | .dynamodb/ 85 | 86 | .idea/ 87 | .vscode/ 88 | .DS_Store 89 | 90 | dist/ 91 | 92 | *.tsbuildinfo 93 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2022 Ardalan Amini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Events 2 | 3 | `@foxify/events` is a `EventEmitter` alternative for `Node.js` and `browser` that has been optimized for better 4 | performance compared to the native version. 5 | 6 | [![Build Status][BUILD_BADGE]][BUILD_URI] 7 | [![Coverage Status][COVERAGE_BADGE]][COVERAGE_URI] 8 | [![NPM Version][VERSION_BADGE]][NPM_URI] 9 | [![npm bundle size (minified)][MINIFIED_BADGE]][NPM_URI] 10 | [![npm bundle size (minified + gzip)][GZIP_BADGE]][NPM_URI] 11 | [![NPM Monthly Downloads][MONTHLY_DOWNLOADS_BADGE]][NPM_URI] 12 | [![NPM Total Downloads][TOTAL_DOWNLOADS_BADGE]][NPM_URI] 13 | [![Dependencies Status][DEPENDENCY_STATUS_BADGE]][DEPENDENCY_STATUS_URI] 14 | [![Open Issues][OPEN_ISSUES_BADGE]][OPEN_ISSUES_URI] 15 | [![Pull Requests][PR_BADGE]][PR_URI] 16 | [![License][LICENSE_BADGE]][LICENSE_URI] 17 | 18 | This module is API compatible with the `EventEmitter` that ships by default with Node.js but there are some slight 19 | differences: 20 | 21 | - The `newListener` and `removeListener` events have been removed as they are useful only in some uncommon use-cases. 22 | - The `setMaxListeners` and `getMaxListeners` methods are not available. 23 | - Support for custom context for events so there is no need to use `bind`. 24 | - Support for strict events in `TypeScript`. 25 | 26 | ## Table of Contents 27 | 28 | - [Installation](#installation) 29 | - [Usage](#usage) 30 | - [Strict events](#strict-events) 31 | - [Contextual emits](#contextual-emits) 32 | - [Benchmarks](#benchmarks) 33 | - [Tests](#tests) 34 | - [Coverage](#coverage) 35 | - [Versioning](#versioning) 36 | - [Authors](#authors) 37 | - [License](#license) 38 | 39 | ## Installation 40 | 41 | ```shell 42 | npm i @foxify/events 43 | ``` 44 | 45 | ## Usage 46 | 47 | JavaScript: 48 | 49 | ```javascript 50 | const EventEmitter = require("@foxify/events").default; 51 | ``` 52 | 53 | TypeScript: 54 | 55 | ```typescript 56 | import EventEmitter from "@foxify/events"; 57 | ``` 58 | 59 | For the API documentation, please follow the official Node.js [documentation](https://nodejs.org/api/events.html). 60 | 61 | ### Strict events 62 | 63 | > "error" event is always defined by default because of its different behavior 64 | 65 | first create events type (optional) 66 | 67 | ```typescript 68 | type Events = { 69 | foo: (bar: string) => void; 70 | withContextEnforcement: (this: number, bar: number) => void; 71 | } 72 | ``` 73 | 74 | then create a new direct/extended instance 75 | 76 | ```typescript 77 | const eventEmitter = new EventEmitter(); 78 | ``` 79 | 80 | ```typescript 81 | class Emitter extends EventEmitter { 82 | } 83 | 84 | const eventEmitter = new Emitter(); 85 | ``` 86 | 87 | then start emitting & listening to events 88 | 89 | ```typescript 90 | // Works just fine. so don't worry about "ImplicitAny" config, since type of "bar" is defined as "string" 91 | eventEmitter.on("foo", bar => 1); 92 | 93 | // This works fine as well 94 | eventEmitter.on("withContextEnforcement", function (bar) { 95 | return this + bar; 96 | }, 1); 97 | 98 | // Throws an error (TS compile time), since this event requires the "bar" argument of type "string" 99 | eventEmitter.emit("foo"); 100 | 101 | // Works just fine 102 | eventEmitter.emit("foo", "bar"); 103 | ``` 104 | 105 | ### Contextual emits 106 | 107 | We've upgraded the API of the `on`, `once`, `addListener`, `prependListener` and 108 | `prependOnceListener` to accept an extra argument which is the `context` 109 | or `this` value that should be set for the emitted events. This means you no longer have the overhead of an event that 110 | required `bind` in order to get a custom `this` value. 111 | 112 | ```javascript 113 | const eventEmitter = new EventEmitter(); 114 | const context = { foo: "bar" }; 115 | 116 | function listener() { 117 | console.log(this === context); // true 118 | } 119 | 120 | eventEmitter.on("event:1", listener, context); 121 | eventEmitter.once("event:2", listener, context); 122 | eventEmitter.addListener("event:3", listener, context); 123 | eventEmitter.prependListener("event:4", listener, context); 124 | eventEmitter.prependOnceListener("event:5", listener, context); 125 | ``` 126 | 127 | ## Benchmarks 128 | 129 | ```shell 130 | npm run benchmarks 131 | ``` 132 | 133 | ## Tests 134 | 135 | ```shell 136 | npm test 137 | ``` 138 | 139 | ## Coverage 140 | 141 | ```shell 142 | npm run test:coverage 143 | ``` 144 | 145 | ## Versioning 146 | 147 | We use [SemVer](http://semver.org) for versioning. For the versions available, see 148 | the [releases on this repository](https://github.com/foxifyjs/events/releases). 149 | 150 | ## Authors 151 | 152 | - **Ardalan Amini** - *Core Maintainer* - [@ardalanamini](https://github.com/ardalanamini) 153 | 154 | See also the list of [contributors](https://github.com/foxifyjs/events/contributors) who participated in this project. 155 | 156 | ## License 157 | 158 | This project is licensed under the MIT License - see the [LICENSE][LICENSE_URI] file for details 159 | 160 | 161 | [BUILD_BADGE]: https://github.com/foxifyjs/events/workflows/Test/badge.svg 162 | 163 | [BUILD_URI]: https://github.com/foxifyjs/events/actions 164 | 165 | [COVERAGE_BADGE]: https://codecov.io/gh/foxifyjs/events/branch/master/graph/badge.svg 166 | 167 | [COVERAGE_URI]: https://codecov.io/gh/foxifyjs/events 168 | 169 | [VERSION_BADGE]: https://img.shields.io/npm/v/@foxify/events.svg 170 | 171 | [MINIFIED_BADGE]: https://img.shields.io/bundlephobia/min/@foxify/events.svg 172 | 173 | [GZIP_BADGE]: https://img.shields.io/bundlephobia/minzip/@foxify/events.svg 174 | 175 | [MONTHLY_DOWNLOADS_BADGE]: https://img.shields.io/npm/dm/@foxify/events.svg 176 | 177 | [TOTAL_DOWNLOADS_BADGE]: https://img.shields.io/npm/dt/@foxify/events.svg 178 | 179 | [DEPENDENCY_STATUS_BADGE]: https://david-dm.org/foxifyjs/events.svg 180 | 181 | [DEPENDENCY_STATUS_URI]: https://david-dm.org/foxifyjs/events 182 | 183 | [OPEN_ISSUES_BADGE]: https://img.shields.io/github/issues-raw/foxifyjs/events.svg 184 | 185 | [OPEN_ISSUES_URI]: https://github.com/foxifyjs/events/issues?q=is%3Aopen+is%3Aissue 186 | 187 | [PR_BADGE]: https://img.shields.io/badge/PRs-Welcome-brightgreen.svg 188 | 189 | [PR_URI]: https://github.com/foxifyjs/events/pulls 190 | 191 | [LICENSE_BADGE]: https://img.shields.io/github/license/foxifyjs/events.svg 192 | 193 | [LICENSE_URI]: https://github.com/foxifyjs/events/blob/master/LICENSE 194 | 195 | [NPM_URI]: https://www.npmjs.com/package/@foxify/events 196 | -------------------------------------------------------------------------------- /__tests__/emit.ts: -------------------------------------------------------------------------------- 1 | import EventEmitter from "../src"; 2 | 3 | it("should return false when there are not events to emit", () => { 4 | const e = new EventEmitter(); 5 | 6 | expect(e.emit("foo")).toBe(false); 7 | expect(e.emit("bar")).toBe(false); 8 | }); 9 | 10 | it("should emit with context", (done) => { 11 | expect.assertions(3); 12 | 13 | const context = { bar: "baz" }; 14 | const e = new EventEmitter(); 15 | 16 | e.on( 17 | "foo", 18 | function (bar) { 19 | expect(bar).toBe("bar"); 20 | expect(this).toEqual(context); 21 | 22 | done(); 23 | }, 24 | context, 25 | ); 26 | 27 | expect(e.emit("foo", "bar")).toBe(true); 28 | }); 29 | 30 | it("should emit with context, multiple arguments (force apply)", (done) => { 31 | expect.assertions(3); 32 | 33 | const context = { bar: "baz" }; 34 | const e = new EventEmitter(); 35 | 36 | e.on( 37 | "foo", 38 | function (bar) { 39 | expect(bar).toBe("bar"); 40 | expect(this).toEqual(context); 41 | 42 | done(); 43 | }, 44 | context, 45 | ); 46 | 47 | expect(e.emit("foo", "bar", 1, 2, 3, 4, 5, 6, 7, 8, 9, 0)).toBe(true); 48 | }); 49 | 50 | it("should be able to emit the function with multiple arguments", () => { 51 | const e = new EventEmitter(); 52 | 53 | for (let i = 0; i < 100; i++) { 54 | (function (j) { 55 | const args: number[] = []; 56 | 57 | for (let i = 0; i < j; i++) { 58 | args.push(j); 59 | } 60 | 61 | e.once("args", function () { 62 | expect(arguments.length).toBe(args.length); 63 | }); 64 | 65 | e.emit.apply(e, ["args", ...args]); 66 | })(i); 67 | } 68 | }); 69 | 70 | it("should be able to emit the function with multiple arguments, multiple listeners", () => { 71 | const e = new EventEmitter(); 72 | 73 | for (let i = 0; i < 100; i++) { 74 | (function (j) { 75 | const args: number[] = []; 76 | 77 | for (let i = 0; i < j; i++) { 78 | args.push(j); 79 | } 80 | 81 | e.once("args", function () { 82 | expect(arguments.length).toBe(args.length); 83 | }); 84 | 85 | e.emit.apply(e, ["args", ...args]); 86 | })(i); 87 | } 88 | }); 89 | 90 | it("should be able to emit with context, multiple listeners (force loop)", () => { 91 | const e = new EventEmitter(); 92 | 93 | e.on( 94 | "foo", 95 | function (bar) { 96 | expect(this).toEqual({ foo: "bar" }); 97 | expect(bar).toBe("bar"); 98 | }, 99 | { foo: "bar" }, 100 | ); 101 | 102 | e.on( 103 | "foo", 104 | function (bar) { 105 | expect(this).toEqual({ bar: "baz" }); 106 | expect(bar).toBe("bar"); 107 | }, 108 | { bar: "baz" }, 109 | ); 110 | 111 | e.emit("foo", "bar"); 112 | }); 113 | 114 | it("should be able to emit with different contexts", () => { 115 | const e = new EventEmitter(); 116 | let pattern = ""; 117 | 118 | function writer(this: string) { 119 | pattern += this; 120 | } 121 | 122 | e.on("write", writer, "foo"); 123 | e.on("write", writer, "baz"); 124 | e.once("write", writer, "bar"); 125 | 126 | e.emit("write"); 127 | expect(pattern).toBe("foobazbar"); 128 | }); 129 | 130 | it("should return true when there are events to emit", () => { 131 | const e = new EventEmitter(); 132 | let called = 0; 133 | 134 | e.on("foo", function () { 135 | called++; 136 | }); 137 | 138 | expect(e.emit("foo")).toBe(true); 139 | expect(e.emit("foob")).toBe(false); 140 | expect(called).toBe(1); 141 | }); 142 | 143 | it("receives the emitted events", (done) => { 144 | const e = new EventEmitter(); 145 | 146 | e.on("data", function (a, b, c, d, undef) { 147 | expect(a).toBe("foo"); 148 | expect(b).toEqual(e); 149 | expect(c).toBeInstanceOf(Date); 150 | expect(undef).toBe(undefined); 151 | expect(arguments.length).toBe(3); 152 | 153 | done(); 154 | }); 155 | 156 | e.emit("data", "foo", e, new Date()); 157 | }); 158 | 159 | it("emits to all event listeners", () => { 160 | const e = new EventEmitter(); 161 | const pattern: string[] = []; 162 | 163 | e.on("foo", function () { 164 | pattern.push("foo1"); 165 | }); 166 | 167 | e.on("foo", function () { 168 | pattern.push("foo2"); 169 | }); 170 | 171 | e.emit("foo"); 172 | 173 | expect(pattern.join(";")).toBe("foo1;foo2"); 174 | }); 175 | 176 | it("should throw the error", () => { 177 | const e = new EventEmitter(); 178 | 179 | const error = new Error("test"); 180 | 181 | expect(() => e.emit("error", error)).toThrow(error); 182 | }); 183 | -------------------------------------------------------------------------------- /__tests__/eventNames.ts: -------------------------------------------------------------------------------- 1 | import EventEmitter from "../src"; 2 | 3 | it("returns an empty array when there are no events", () => { 4 | const e = new EventEmitter(); 5 | 6 | expect(e.eventNames()).toEqual([]); 7 | 8 | // eslint-disable-next-line @typescript-eslint/no-empty-function 9 | e.on("foo", () => {}); 10 | e.removeAllListeners("foo"); 11 | 12 | expect(e.eventNames()).toEqual([]); 13 | }); 14 | 15 | it("returns an array listing the events that have listeners", () => { 16 | const e = new EventEmitter(); 17 | 18 | // eslint-disable-next-line @typescript-eslint/no-empty-function 19 | function bar() {} 20 | 21 | // eslint-disable-next-line @typescript-eslint/no-empty-function 22 | e.on("foo", () => {}); 23 | e.on("bar", bar); 24 | 25 | expect(e.eventNames()).toEqual(["foo", "bar"]); 26 | e.removeListener("bar", bar); 27 | expect(e.eventNames()).toEqual(["foo"]); 28 | }); 29 | -------------------------------------------------------------------------------- /__tests__/listenerCount.ts: -------------------------------------------------------------------------------- 1 | import EventEmitter from "../src"; 2 | 3 | it("returns the number of listeners for a given event", () => { 4 | const e = new EventEmitter(); 5 | 6 | expect(e.listenerCount("foo")).toBe(0); 7 | 8 | // eslint-disable-next-line @typescript-eslint/no-empty-function 9 | e.on("foo", function () {}); 10 | 11 | expect(e.listenerCount("foo")).toBe(1); 12 | 13 | // eslint-disable-next-line @typescript-eslint/no-empty-function 14 | e.on("foo", function () {}); 15 | 16 | expect(e.listenerCount("foo")).toBe(2); 17 | }); 18 | -------------------------------------------------------------------------------- /__tests__/listeners.ts: -------------------------------------------------------------------------------- 1 | import EventEmitter from "../src"; 2 | 3 | it("returns an empty array if no listeners are specified", () => { 4 | const e = new EventEmitter(); 5 | 6 | expect(e.listeners("foo")).toBeInstanceOf(Array); 7 | expect(e.listeners("foo").length).toBe(0); 8 | }); 9 | 10 | it("returns an array of function", () => { 11 | const e = new EventEmitter(); 12 | 13 | // eslint-disable-next-line @typescript-eslint/no-empty-function 14 | function foo() {} 15 | 16 | e.on("foo", foo); 17 | 18 | // eslint-disable-next-line @typescript-eslint/no-empty-function 19 | function bar() {} 20 | 21 | e.on("foo", bar); 22 | 23 | const listeners = e.listeners("foo"); 24 | 25 | expect(listeners).toBeInstanceOf(Array); 26 | expect(listeners.length).toBe(2); 27 | expect(listeners).toEqual([foo, bar]); 28 | }); 29 | 30 | it("is not vulnerable to modifications", () => { 31 | const e = new EventEmitter(); 32 | 33 | // eslint-disable-next-line @typescript-eslint/no-empty-function 34 | function foo() {} 35 | 36 | e.on("foo", foo); 37 | 38 | expect(e.listeners("foo")).toEqual([foo]); 39 | 40 | e.listeners("foo").length = 0; 41 | 42 | expect(e.listeners("foo")).toEqual([foo]); 43 | }); 44 | -------------------------------------------------------------------------------- /__tests__/on.ts: -------------------------------------------------------------------------------- 1 | import EventEmitter from "../src"; 2 | 3 | it("should listen to all the emits", () => { 4 | const e = new EventEmitter(); 5 | let calls = 0; 6 | 7 | e.on("foo", () => { 8 | calls++; 9 | }); 10 | 11 | e.emit("foo"); 12 | e.emit("foo"); 13 | e.emit("foo"); 14 | e.emit("foo"); 15 | e.emit("foo"); 16 | 17 | expect(e.listeners("foo").length).toBe(1); 18 | expect(calls).toBe(5); 19 | }); 20 | -------------------------------------------------------------------------------- /__tests__/once.ts: -------------------------------------------------------------------------------- 1 | import EventEmitter from "../src"; 2 | 3 | it("should only emits it once (case 1)", () => { 4 | const e = new EventEmitter(); 5 | 6 | const listener = jest.fn(); 7 | 8 | e.once("foo", listener); 9 | 10 | e.emit("foo"); 11 | e.emit("foo"); 12 | e.emit("foo"); 13 | e.emit("foo"); 14 | e.emit("foo"); 15 | 16 | expect(e.listeners("foo").length).toBe(0); 17 | expect(listener).toBeCalledTimes(1); 18 | }); 19 | 20 | it("should only emits it once (case 2)", () => { 21 | const e = new EventEmitter(); 22 | 23 | const listener = jest.fn(); 24 | 25 | e.once("foo", listener); 26 | 27 | e.emit("foo"); 28 | e.emit("foo"); 29 | 30 | expect(e.listeners("foo").length).toBe(0); 31 | expect(listener).toBeCalledTimes(1); 32 | }); 33 | 34 | it("should only emits it once (case 3)", () => { 35 | const e = new EventEmitter(); 36 | 37 | const listener = jest.fn(); 38 | 39 | e.once("foo", listener); 40 | e.once("foo", listener); 41 | 42 | e.emit("foo"); 43 | 44 | expect(e.listeners("foo").length).toBe(0); 45 | expect(listener).toBeCalledTimes(2); 46 | }); 47 | 48 | it("only emits once if emits are nested inside the listener", () => { 49 | const e = new EventEmitter(); 50 | let calls = 0; 51 | 52 | e.once("foo", () => { 53 | calls++; 54 | e.emit("foo"); 55 | }); 56 | 57 | e.emit("foo"); 58 | expect(e.listeners("foo").length).toBe(0); 59 | expect(calls).toBe(1); 60 | }); 61 | 62 | it("only emits once for multiple events", () => { 63 | const e = new EventEmitter(); 64 | let multi = 0; 65 | let foo = 0; 66 | let bar = 0; 67 | 68 | e.once("foo", () => { 69 | foo++; 70 | }); 71 | 72 | e.once("foo", () => { 73 | bar++; 74 | }); 75 | 76 | e.on("foo", () => { 77 | multi++; 78 | }); 79 | 80 | e.emit("foo"); 81 | e.emit("foo"); 82 | e.emit("foo"); 83 | e.emit("foo"); 84 | e.emit("foo"); 85 | 86 | expect(e.listeners("foo").length).toBe(1); 87 | expect(foo).toBe(1); 88 | expect(bar).toBe(1); 89 | expect(multi).toBe(5); 90 | }); 91 | 92 | it("only emits once with context", (done) => { 93 | expect.assertions(4); 94 | 95 | const context = { foo: "bar" }; 96 | const e = new EventEmitter(); 97 | 98 | e.once( 99 | "foo", 100 | function (bar) { 101 | expect(this).toEqual(context); 102 | expect(bar).toBe("bar"); 103 | 104 | done(); 105 | }, 106 | context, 107 | ); 108 | 109 | expect(e.emit("foo", "bar")).toBe(true); 110 | expect(e.listenerCount("foo")).toBe(0); 111 | }); 112 | -------------------------------------------------------------------------------- /__tests__/prependListener.ts: -------------------------------------------------------------------------------- 1 | import EventEmitter from "../src"; 2 | 3 | it("should prepend the listener", () => { 4 | const e = new EventEmitter(); 5 | const calls: number[] = []; 6 | 7 | e.on("foo", () => { 8 | calls.push(1); 9 | }); 10 | 11 | e.prependListener("foo", () => { 12 | calls.push(2); 13 | }); 14 | 15 | e.emit("foo"); 16 | 17 | expect(e.listeners("foo").length).toBe(2); 18 | expect(e.listenerCount("foo")).toBe(2); 19 | expect(calls).toEqual([2, 1]); 20 | }); 21 | -------------------------------------------------------------------------------- /__tests__/prependOnceListener.ts: -------------------------------------------------------------------------------- 1 | import EventEmitter from "../src"; 2 | 3 | it("should prepend the listener and remove it after first event", () => { 4 | const e = new EventEmitter(); 5 | const calls: number[] = []; 6 | 7 | e.on("foo", () => { 8 | calls.push(1); 9 | }); 10 | 11 | e.on("foo", () => { 12 | calls.push(2); 13 | }); 14 | 15 | e.prependOnceListener("foo", () => { 16 | calls.push(3); 17 | }); 18 | 19 | e.emit("foo"); 20 | e.emit("foo"); 21 | 22 | expect(e.listeners("foo").length).toBe(2); 23 | expect(e.listenerCount("foo")).toBe(2); 24 | expect(calls).toEqual([3, 1, 2, 1, 2]); 25 | }); 26 | -------------------------------------------------------------------------------- /__tests__/removeAllListeners.ts: -------------------------------------------------------------------------------- 1 | import EventEmitter from "../src"; 2 | 3 | it("removes all events for the specified events", () => { 4 | const e = new EventEmitter(); 5 | 6 | e.on("foo", () => { 7 | throw new Error("oops"); 8 | }); 9 | e.on("foo", () => { 10 | throw new Error("oops"); 11 | }); 12 | e.on("bar", () => { 13 | throw new Error("oops"); 14 | }); 15 | e.on("aaa", () => { 16 | throw new Error("oops"); 17 | }); 18 | 19 | expect(e.removeAllListeners("foo")).toEqual(e); 20 | expect(e.listeners("foo").length).toBe(0); 21 | expect(e.listeners("bar").length).toBe(1); 22 | expect(e.listeners("aaa").length).toBe(1); 23 | 24 | expect(e.removeAllListeners("bar")).toEqual(e); 25 | expect(e.removeAllListeners("aaa")).toEqual(e); 26 | 27 | expect(e.emit("foo")).toBe(false); 28 | expect(e.emit("bar")).toBe(false); 29 | expect(e.emit("aaa")).toBe(false); 30 | }); 31 | 32 | it("removes all events, literally!", () => { 33 | const e = new EventEmitter(); 34 | 35 | e.on("foo", () => { 36 | throw new Error("oops"); 37 | }); 38 | e.on("foo", () => { 39 | throw new Error("oops"); 40 | }); 41 | e.on("bar", () => { 42 | throw new Error("oops"); 43 | }); 44 | e.on("aaa", () => { 45 | throw new Error("oops"); 46 | }); 47 | 48 | expect(e.removeAllListeners()).toEqual(e); 49 | expect(e.listeners("foo").length).toBe(0); 50 | expect(e.listeners("bar").length).toBe(0); 51 | expect(e.listeners("aaa").length).toBe(0); 52 | 53 | expect(e.emit("foo")).toBe(false); 54 | expect(e.emit("bar")).toBe(false); 55 | expect(e.emit("aaa")).toBe(false); 56 | }); 57 | 58 | it("should not fail if the events don't exist", () => { 59 | const e = new EventEmitter(); 60 | 61 | expect(e.removeAllListeners("not-found")).toEqual(e); 62 | }); 63 | -------------------------------------------------------------------------------- /__tests__/removeListener.ts: -------------------------------------------------------------------------------- 1 | import EventEmitter from "../src"; 2 | 3 | it("removes only the first listener matching the specified listener", () => { 4 | const e = new EventEmitter(); 5 | 6 | // eslint-disable-next-line @typescript-eslint/no-empty-function 7 | function foo() {} 8 | 9 | // eslint-disable-next-line @typescript-eslint/no-empty-function 10 | function bar() {} 11 | 12 | // eslint-disable-next-line @typescript-eslint/no-empty-function 13 | function baz() {} 14 | 15 | e.on("foo", foo); 16 | e.on("bar", bar); 17 | e.on("bar", baz); 18 | 19 | expect(e.removeListener("foo", bar)).toEqual(e); 20 | expect(e.listeners("bar")).toEqual([bar, baz]); 21 | expect(e.listeners("foo")).toEqual([foo]); 22 | 23 | expect(e.removeListener("foo", foo)).toEqual(e); 24 | expect(e.listeners("bar")).toEqual([bar, baz]); 25 | expect(e.listeners("foo")).toEqual([]); 26 | 27 | expect(e.removeListener("bar", bar)).toEqual(e); 28 | expect(e.listeners("bar")).toEqual([baz]); 29 | 30 | expect(e.removeListener("bar", baz)).toEqual(e); 31 | expect(e.listeners("bar")).toEqual([]); 32 | 33 | e.on("foo", foo); 34 | e.on("foo", foo); 35 | e.on("bar", bar); 36 | 37 | expect(e.removeListener("foo", foo)).toEqual(e); 38 | expect(e.listeners("bar")).toEqual([bar]); 39 | expect(e.listeners("foo")).toEqual([]); 40 | }); 41 | 42 | it("should not fail if the events don't exist", () => { 43 | const e = new EventEmitter(); 44 | 45 | expect(e.removeListener("not-found", () => 1)).toEqual(e); 46 | }); 47 | -------------------------------------------------------------------------------- /__tests__/symbols.ts: -------------------------------------------------------------------------------- 1 | import EventEmitter from "../src"; 2 | 3 | it("should work with ES6 symbols", (done) => { 4 | const e = new EventEmitter(); 5 | const event = Symbol("cows"); 6 | const unknown = Symbol("moo"); 7 | 8 | e.on(event, function foo(arg) { 9 | expect(e.listenerCount(unknown)).toBe(0); 10 | expect(e.listeners(unknown)).toEqual([]); 11 | expect(arg).toBe("bar"); 12 | 13 | function bar(onced: unknown) { 14 | expect(e.listenerCount(unknown)).toBe(0); 15 | expect(e.listeners(unknown)).toEqual([]); 16 | expect(onced).toBe("foo"); 17 | done(); 18 | } 19 | 20 | e.once(unknown, bar); 21 | 22 | expect(e.listenerCount(event)).toBe(1); 23 | expect(e.listeners(event)).toEqual([foo]); 24 | expect(e.listenerCount(unknown)).toBe(1); 25 | expect(e.listeners(unknown)).toEqual([bar]); 26 | 27 | e.removeAllListeners(event); 28 | 29 | expect(e.listenerCount(event)).toBe(0); 30 | expect(e.listeners(event)).toEqual([]); 31 | expect(e.emit(unknown, "foo")).toBe(true); 32 | }); 33 | 34 | expect(e.emit(unknown, "bar")).toBe(false); 35 | expect(e.emit(event, "bar")).toBe(true); 36 | }); 37 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /benchmarks/README.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | ```shell 4 | Starting benchmark add-remove.js 5 | 6 | ╔══════════════════════╤══════════════════════╤══════════════════════╤══════════════════════╤══════════════════════╗ 7 | ║ events │ 28,646,139 ops/sec │ 1X │ ±0.29% │ 97 runs sampled ║ 8 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 9 | ║ @foxify/events │ 145,201,544 ops/sec │ 5.07X │ ±0.12% │ 101 runs sampled ║ 10 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 11 | ║ @foxify/events (v1) │ 131,714,369 ops/sec │ 4.6X │ ±0.25% │ 100 runs sampled ║ 12 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 13 | ║ eventemitter2 │ 9,997,430 ops/sec │ 0.35X │ ±0.25% │ 101 runs sampled ║ 14 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 15 | ║ eventemitter3 │ 118,168,966 ops/sec │ 4.13X │ ±0.66% │ 101 runs sampled ║ 16 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 17 | ║ drip │ 155,560,564 ops/sec │ 5.43X │ ±0.07% │ 97 runs sampled ║ 18 | ╚══════════════════════╧══════════════════════╧══════════════════════╧══════════════════════╧══════════════════════╝ 19 | > Fastest is [ 'drip' ] 20 | 21 | Starting benchmark hundreds.js 22 | 23 | ╔══════════════════════╤══════════════════════╤══════════════════════╤══════════════════════╤══════════════════════╗ 24 | ║ events │ 682,944 ops/sec │ 1X │ ±0.32% │ 98 runs sampled ║ 25 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 26 | ║ @foxify/events │ 1,749,384 ops/sec │ 2.56X │ ±0.21% │ 100 runs sampled ║ 27 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 28 | ║ @foxify/events (v1) │ 1,708,705 ops/sec │ 2.5X │ ±0.14% │ 100 runs sampled ║ 29 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 30 | ║ eventemitter2 │ 635,410 ops/sec │ 0.93X │ ±0.33% │ 101 runs sampled ║ 31 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 32 | ║ eventemitter3 │ 751,019 ops/sec │ 1.1X │ ±0.74% │ 97 runs sampled ║ 33 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 34 | ║ drip │ 885,395 ops/sec │ 1.3X │ ±0.49% │ 90 runs sampled ║ 35 | ╚══════════════════════╧══════════════════════╧══════════════════════╧══════════════════════╧══════════════════════╝ 36 | > Fastest is [ '@foxify/events' ] 37 | 38 | Starting benchmark context.js 39 | 40 | ╔══════════════════════╤══════════════════════╤══════════════════════╤══════════════════════╤══════════════════════╗ 41 | ║ events │ 65,013,718 ops/sec │ 1X │ ±0.2% │ 96 runs sampled ║ 42 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 43 | ║ @foxify/events │ 1,033,038,945 ops/se │ 15.89X │ ±0.06% │ 102 runs sampled ║ 44 | ║ │ c │ │ │ ║ 45 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 46 | ║ @foxify/events (v1) │ 154,303,958 ops/sec │ 2.37X │ ±0.1% │ 102 runs sampled ║ 47 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 48 | ║ eventemitter2 │ 92,690,720 ops/sec │ 1.43X │ ±0.22% │ 100 runs sampled ║ 49 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 50 | ║ eventemitter3 │ 112,494,738 ops/sec │ 1.73X │ ±0.1% │ 100 runs sampled ║ 51 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 52 | ║ drip │ 158,395,065 ops/sec │ 2.44X │ ±0.06% │ 102 runs sampled ║ 53 | ╚══════════════════════╧══════════════════════╧══════════════════════╧══════════════════════╧══════════════════════╝ 54 | > Fastest is [ '@foxify/events' ] 55 | 56 | Starting benchmark emit-multiple-listeners.js 57 | 58 | ╔══════════════════════╤══════════════════════╤══════════════════════╤══════════════════════╤══════════════════════╗ 59 | ║ events │ 27,697,860 ops/sec │ 1X │ ±0.24% │ 97 runs sampled ║ 60 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 61 | ║ @foxify/events │ 50,207,479 ops/sec │ 1.81X │ ±0.07% │ 98 runs sampled ║ 62 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 63 | ║ @foxify/events (v1) │ 48,154,925 ops/sec │ 1.74X │ ±0.06% │ 102 runs sampled ║ 64 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 65 | ║ eventemitter2 │ 29,161,090 ops/sec │ 1.05X │ ±0.21% │ 101 runs sampled ║ 66 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 67 | ║ eventemitter3 │ 44,958,609 ops/sec │ 1.62X │ ±0.07% │ 102 runs sampled ║ 68 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 69 | ║ drip │ 54,894,981 ops/sec │ 1.98X │ ±0.11% │ 102 runs sampled ║ 70 | ╚══════════════════════╧══════════════════════╧══════════════════════╧══════════════════════╧══════════════════════╝ 71 | > Fastest is [ 'drip' ] 72 | 73 | Starting benchmark emit.js 74 | 75 | ╔══════════════════════╤══════════════════════╤══════════════════════╤══════════════════════╤══════════════════════╗ 76 | ║ events │ 73,017,308 ops/sec │ 1X │ ±0.21% │ 96 runs sampled ║ 77 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 78 | ║ @foxify/events │ 1,033,579,034 ops/se │ 14.16X │ ±0.06% │ 102 runs sampled ║ 79 | ║ │ c │ │ │ ║ 80 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 81 | ║ @foxify/events (v1) │ 154,521,677 ops/sec │ 2.12X │ ±0.06% │ 100 runs sampled ║ 82 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 83 | ║ eventemitter2 │ 111,389,108 ops/sec │ 1.53X │ ±0.19% │ 101 runs sampled ║ 84 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 85 | ║ eventemitter3 │ 117,730,413 ops/sec │ 1.61X │ ±0.11% │ 97 runs sampled ║ 86 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 87 | ║ drip │ 158,327,253 ops/sec │ 2.17X │ ±0.12% │ 98 runs sampled ║ 88 | ╚══════════════════════╧══════════════════════╧══════════════════════╧══════════════════════╧══════════════════════╝ 89 | > Fastest is [ '@foxify/events' ] 90 | 91 | Starting benchmark once.js 92 | 93 | ╔══════════════════════╤══════════════════════╤══════════════════════╤══════════════════════╤══════════════════════╗ 94 | ║ events │ 16,007,163 ops/sec │ 1X │ ±0.36% │ 92 runs sampled ║ 95 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 96 | ║ @foxify/events │ 122,788,576 ops/sec │ 7.67X │ ±0.09% │ 97 runs sampled ║ 97 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 98 | ║ @foxify/events (v1) │ 75,537,370 ops/sec │ 4.72X │ ±0.18% │ 95 runs sampled ║ 99 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 100 | ║ eventemitter2 │ 7,699,803 ops/sec │ 0.48X │ ±0.45% │ 99 runs sampled ║ 101 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 102 | ║ eventemitter3 │ 56,224,261 ops/sec │ 3.51X │ ±0.12% │ 101 runs sampled ║ 103 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 104 | ║ drip │ 38,392,426 ops/sec │ 2.4X │ ±0.11% │ 100 runs sampled ║ 105 | ╚══════════════════════╧══════════════════════╧══════════════════════╧══════════════════════╧══════════════════════╝ 106 | > Fastest is [ '@foxify/events' ] 107 | 108 | Starting benchmark listeners.js 109 | 110 | ╔══════════════════════╤══════════════════════╤══════════════════════╤══════════════════════╤══════════════════════╗ 111 | ║ events │ 28,647,172 ops/sec │ 1X │ ±0.39% │ 94 runs sampled ║ 112 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 113 | ║ @foxify/events │ 29,743,920 ops/sec │ 1.04X │ ±0.06% │ 99 runs sampled ║ 114 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 115 | ║ @foxify/events (v1) │ 29,267,858 ops/sec │ 1.02X │ ±0.6% │ 96 runs sampled ║ 116 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 117 | ║ eventemitter2 │ 112,778,260 ops/sec │ 3.94X │ ±0.09% │ 98 runs sampled ║ 118 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 119 | ║ eventemitter3 │ 29,749,040 ops/sec │ 1.04X │ ±0.13% │ 99 runs sampled ║ 120 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 121 | ║ drip │ 156,366,882 ops/sec │ 5.46X │ ±0.13% │ 103 runs sampled ║ 122 | ╚══════════════════════╧══════════════════════╧══════════════════════╧══════════════════════╧══════════════════════╝ 123 | > Fastest is [ 'drip' ] 124 | 125 | Starting benchmark init.js 126 | 127 | ╔══════════════════════╤══════════════════════╤══════════════════════╤══════════════════════╤══════════════════════╗ 128 | ║ events │ 100,584,511 ops/sec │ 1X │ ±0.63% │ 96 runs sampled ║ 129 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 130 | ║ @foxify/events │ 997,988,271 ops/sec │ 9.92X │ ±0.08% │ 103 runs sampled ║ 131 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 132 | ║ @foxify/events (v1) │ 998,259,696 ops/sec │ 9.92X │ ±0.07% │ 103 runs sampled ║ 133 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 134 | ║ eventemitter2 │ 995,046,084 ops/sec │ 9.89X │ ±0.11% │ 102 runs sampled ║ 135 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 136 | ║ eventemitter3 │ 997,660,657 ops/sec │ 9.92X │ ±0.12% │ 103 runs sampled ║ 137 | ╟──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────╢ 138 | ║ drip │ 1,035,602,545 ops/se │ 10.3X │ ±0.18% │ 100 runs sampled ║ 139 | ║ │ c │ │ │ ║ 140 | ╚══════════════════════╧══════════════════════╧══════════════════════╧══════════════════════╧══════════════════════╝ 141 | > Fastest is [ 'drip' ] 142 | ``` 143 | -------------------------------------------------------------------------------- /benchmarks/add-remove.js: -------------------------------------------------------------------------------- 1 | const { createStream } = require("table"); 2 | const { Suite } = require("benchmark"); 3 | const { EventEmitter } = require("events"); 4 | const { EventEmitter2 } = require("eventemitter2"); 5 | const EventEmitter3 = require("eventemitter3"); 6 | const { EventEmitter: DripEmitter } = require("drip"); 7 | const { EventEmitter: FoxifyEmitterV1 } = require("eventsV1"); 8 | const FoxifyEmitter = require("..").default; 9 | 10 | const stream = createStream({ 11 | columnDefault: { 12 | alignment: "center", 13 | width: 20, 14 | }, 15 | columnCount: 5, 16 | }); 17 | 18 | let base; 19 | 20 | function handle() { 21 | return 1; 22 | } 23 | 24 | const eventEmitter = new EventEmitter(); 25 | const eventEmitter2 = new EventEmitter2(); 26 | const eventEmitter3 = new EventEmitter3(); 27 | const dripEmitter = new DripEmitter(); 28 | const foxifyEmitterV1 = new FoxifyEmitterV1(); 29 | const foxifyEmitter = new FoxifyEmitter(); 30 | 31 | new Suite() 32 | .add("events", () => { 33 | eventEmitter.on("foo", handle); 34 | eventEmitter.removeListener("foo", handle); 35 | }) 36 | .add("@foxify/events", () => { 37 | foxifyEmitter.on("foo", handle); 38 | foxifyEmitter.removeListener("foo", handle); 39 | }) 40 | .add("@foxify/events (v1)", () => { 41 | foxifyEmitterV1.on("foo", handle); 42 | foxifyEmitterV1.removeListener("foo", handle); 43 | }) 44 | .add("eventemitter2", () => { 45 | eventEmitter2.on("foo", handle); 46 | eventEmitter2.removeListener("foo", handle); 47 | }) 48 | .add("eventemitter3", () => { 49 | eventEmitter3.on("foo", handle); 50 | eventEmitter3.removeListener("foo", handle); 51 | }) 52 | .add("drip", () => { 53 | dripEmitter.on("foo", handle); 54 | dripEmitter.removeListener("foo", handle); 55 | }) 56 | .on("cycle", (e) => { 57 | const { name, hz, stats } = e.target; 58 | 59 | if (name === "events") base = hz; 60 | 61 | const size = stats.sample.length; 62 | 63 | stream.write([ 64 | name, 65 | `${hz.toLocaleString("en-US", { 66 | maximumFractionDigits: hz < 100 ? 2 : 0, 67 | })} ops/sec`, 68 | `${(hz / base).toLocaleString("en-US", { 69 | maximumFractionDigits: 2, 70 | })}X`, 71 | `\xb1${stats.rme.toLocaleString("en-US", { 72 | maximumFractionDigits: 2, 73 | })}%`, 74 | `${size} run${size === 1 ? "" : "s"} sampled`, 75 | ]); 76 | }) 77 | .on("complete", function onComplete() { 78 | console.log("\n> Fastest is %s", this.filter("fastest").map("name")); 79 | }) 80 | .run(); 81 | -------------------------------------------------------------------------------- /benchmarks/context.js: -------------------------------------------------------------------------------- 1 | const { createStream } = require("table"); 2 | const { Suite } = require("benchmark"); 3 | const { EventEmitter } = require("events"); 4 | const { EventEmitter2 } = require("eventemitter2"); 5 | const EventEmitter3 = require("eventemitter3"); 6 | const { EventEmitter: DripEmitter } = require("drip"); 7 | const { EventEmitter: FoxifyEmitterV1 } = require("eventsV1"); 8 | const FoxifyEmitter = require("..").default; 9 | 10 | const stream = createStream({ 11 | columnDefault: { 12 | alignment: "center", 13 | width: 20, 14 | }, 15 | columnCount: 5, 16 | }); 17 | 18 | let base; 19 | 20 | const ctx = { foo: "bar" }; 21 | 22 | function handle() { 23 | return this.foo; 24 | } 25 | 26 | const eventEmitter = new EventEmitter(); 27 | const eventEmitter2 = new EventEmitter2(); 28 | const eventEmitter3 = new EventEmitter3(); 29 | const dripEmitter = new DripEmitter(); 30 | const foxifyEmitterV1 = new FoxifyEmitterV1(); 31 | const foxifyEmitter = new FoxifyEmitter(); 32 | 33 | eventEmitter.on("foo", handle.bind(ctx)); 34 | eventEmitter2.on("foo", handle.bind(ctx)); 35 | eventEmitter3.on("foo", handle, ctx); 36 | dripEmitter.on("foo", handle.bind(ctx)); 37 | foxifyEmitterV1.on("foo", handle, ctx); 38 | foxifyEmitter.on("foo", handle, ctx); 39 | 40 | new Suite() 41 | .add("events", () => { 42 | eventEmitter.emit("foo"); 43 | }) 44 | .add("@foxify/events", () => { 45 | foxifyEmitter.emit("foo"); 46 | }) 47 | .add("@foxify/events (v1)", () => { 48 | foxifyEmitterV1.emit("foo"); 49 | }) 50 | .add("eventemitter2", () => { 51 | eventEmitter2.emit("foo"); 52 | }) 53 | .add("eventemitter3", () => { 54 | eventEmitter3.emit("foo"); 55 | }) 56 | .add("drip", () => { 57 | dripEmitter.emit("foo"); 58 | }) 59 | .on("cycle", (e) => { 60 | const { name, hz, stats } = e.target; 61 | 62 | if (name === "events") base = hz; 63 | 64 | const size = stats.sample.length; 65 | 66 | stream.write([ 67 | name, 68 | `${hz.toLocaleString("en-US", { 69 | maximumFractionDigits: hz < 100 ? 2 : 0, 70 | })} ops/sec`, 71 | `${(hz / base).toLocaleString("en-US", { 72 | maximumFractionDigits: 2, 73 | })}X`, 74 | `\xb1${stats.rme.toLocaleString("en-US", { 75 | maximumFractionDigits: 2, 76 | })}%`, 77 | `${size} run${size === 1 ? "" : "s"} sampled`, 78 | ]); 79 | }) 80 | .on("complete", function onComplete() { 81 | console.log("\n> Fastest is %s", this.filter("fastest").map("name")); 82 | }) 83 | .run(); 84 | -------------------------------------------------------------------------------- /benchmarks/emit-multiple-listeners.js: -------------------------------------------------------------------------------- 1 | const { createStream } = require("table"); 2 | const { Suite } = require("benchmark"); 3 | const { EventEmitter } = require("events"); 4 | const { EventEmitter2 } = require("eventemitter2"); 5 | const EventEmitter3 = require("eventemitter3"); 6 | const { EventEmitter: DripEmitter } = require("drip"); 7 | const { EventEmitter: FoxifyEmitterV1 } = require("eventsV1"); 8 | const FoxifyEmitter = require("..").default; 9 | 10 | const stream = createStream({ 11 | columnDefault: { 12 | alignment: "center", 13 | width: 20, 14 | }, 15 | columnCount: 5, 16 | }); 17 | 18 | let base; 19 | 20 | function foo() { 21 | return 1; 22 | } 23 | 24 | function bar() { 25 | return 2; 26 | } 27 | 28 | function baz() { 29 | return 3; 30 | } 31 | 32 | const eventEmitter = new EventEmitter(); 33 | const eventEmitter2 = new EventEmitter2(); 34 | const eventEmitter3 = new EventEmitter3(); 35 | const dripEmitter = new DripEmitter(); 36 | const foxifyEmitterV1 = new FoxifyEmitterV1(); 37 | const foxifyEmitter = new FoxifyEmitter(); 38 | 39 | eventEmitter.on("foo", foo).on("foo", bar).on("foo", baz); 40 | eventEmitter2.on("foo", foo).on("foo", bar).on("foo", baz); 41 | eventEmitter3.on("foo", foo).on("foo", bar).on("foo", baz); 42 | dripEmitter.on("foo", foo).on("foo", bar).on("foo", baz); 43 | foxifyEmitterV1.on("foo", foo).on("foo", bar).on("foo", baz); 44 | foxifyEmitter.on("foo", foo).on("foo", bar).on("foo", baz); 45 | 46 | new Suite() 47 | .add("events", () => { 48 | eventEmitter.emit("foo"); 49 | }) 50 | .add("@foxify/events", () => { 51 | foxifyEmitter.emit("foo"); 52 | }) 53 | .add("@foxify/events (v1)", () => { 54 | foxifyEmitterV1.emit("foo"); 55 | }) 56 | .add("eventemitter2", () => { 57 | eventEmitter2.emit("foo"); 58 | }) 59 | .add("eventemitter3", () => { 60 | eventEmitter3.emit("foo"); 61 | }) 62 | .add("drip", () => { 63 | dripEmitter.emit("foo"); 64 | }) 65 | .on("cycle", (e) => { 66 | const { name, hz, stats } = e.target; 67 | 68 | if (name === "events") base = hz; 69 | 70 | const size = stats.sample.length; 71 | 72 | stream.write([ 73 | name, 74 | `${hz.toLocaleString("en-US", { 75 | maximumFractionDigits: hz < 100 ? 2 : 0, 76 | })} ops/sec`, 77 | `${(hz / base).toLocaleString("en-US", { 78 | maximumFractionDigits: 2, 79 | })}X`, 80 | `\xb1${stats.rme.toLocaleString("en-US", { 81 | maximumFractionDigits: 2, 82 | })}%`, 83 | `${size} run${size === 1 ? "" : "s"} sampled`, 84 | ]); 85 | }) 86 | .on("complete", function onComplete() { 87 | console.log("\n> Fastest is %s", this.filter("fastest").map("name")); 88 | }) 89 | .run(); 90 | -------------------------------------------------------------------------------- /benchmarks/emit.js: -------------------------------------------------------------------------------- 1 | const { createStream } = require("table"); 2 | const { Suite } = require("benchmark"); 3 | const { EventEmitter } = require("events"); 4 | const { EventEmitter2 } = require("eventemitter2"); 5 | const EventEmitter3 = require("eventemitter3"); 6 | const { EventEmitter: DripEmitter } = require("drip"); 7 | const { EventEmitter: FoxifyEmitterV1 } = require("eventsV1"); 8 | const FoxifyEmitter = require("..").default; 9 | 10 | const stream = createStream({ 11 | columnDefault: { 12 | alignment: "center", 13 | width: 20, 14 | }, 15 | columnCount: 5, 16 | }); 17 | 18 | let base; 19 | 20 | function handle() { 21 | return 1; 22 | } 23 | 24 | const eventEmitter = new EventEmitter(); 25 | const eventEmitter2 = new EventEmitter2(); 26 | const eventEmitter3 = new EventEmitter3(); 27 | const dripEmitter = new DripEmitter(); 28 | const foxifyEmitterV1 = new FoxifyEmitterV1(); 29 | const foxifyEmitter = new FoxifyEmitter(); 30 | 31 | eventEmitter.on("foo", handle); 32 | eventEmitter2.on("foo", handle); 33 | eventEmitter3.on("foo", handle); 34 | dripEmitter.on("foo", handle); 35 | foxifyEmitterV1.on("foo", handle); 36 | foxifyEmitter.on("foo", handle); 37 | 38 | new Suite() 39 | .add("events", () => { 40 | eventEmitter.emit("foo"); 41 | }) 42 | .add("@foxify/events", () => { 43 | foxifyEmitter.emit("foo"); 44 | }) 45 | .add("@foxify/events (v1)", () => { 46 | foxifyEmitterV1.emit("foo"); 47 | }) 48 | .add("eventemitter2", () => { 49 | eventEmitter2.emit("foo"); 50 | }) 51 | .add("eventemitter3", () => { 52 | eventEmitter3.emit("foo"); 53 | }) 54 | .add("drip", () => { 55 | dripEmitter.emit("foo"); 56 | }) 57 | .on("cycle", (e) => { 58 | const { name, hz, stats } = e.target; 59 | 60 | if (name === "events") base = hz; 61 | 62 | const size = stats.sample.length; 63 | 64 | stream.write([ 65 | name, 66 | `${hz.toLocaleString("en-US", { 67 | maximumFractionDigits: hz < 100 ? 2 : 0, 68 | })} ops/sec`, 69 | `${(hz / base).toLocaleString("en-US", { 70 | maximumFractionDigits: 2, 71 | })}X`, 72 | `\xb1${stats.rme.toLocaleString("en-US", { 73 | maximumFractionDigits: 2, 74 | })}%`, 75 | `${size} run${size === 1 ? "" : "s"} sampled`, 76 | ]); 77 | }) 78 | .on("complete", function onComplete() { 79 | console.log("\n> Fastest is %s", this.filter("fastest").map("name")); 80 | }) 81 | .run(); 82 | -------------------------------------------------------------------------------- /benchmarks/hundreds.js: -------------------------------------------------------------------------------- 1 | const { createStream } = require("table"); 2 | const { Suite } = require("benchmark"); 3 | const { EventEmitter } = require("events"); 4 | const { EventEmitter2 } = require("eventemitter2"); 5 | const EventEmitter3 = require("eventemitter3"); 6 | const { EventEmitter: DripEmitter } = require("drip"); 7 | const { EventEmitter: FoxifyEmitterV1 } = require("eventsV1"); 8 | const FoxifyEmitter = require("..").default; 9 | 10 | const stream = createStream({ 11 | columnDefault: { 12 | alignment: "center", 13 | width: 20, 14 | }, 15 | columnCount: 5, 16 | }); 17 | 18 | let base; 19 | 20 | function handle() { 21 | return 1; 22 | } 23 | 24 | const eventEmitter = new EventEmitter(); 25 | const eventEmitter2 = new EventEmitter2(); 26 | const eventEmitter3 = new EventEmitter3(); 27 | const dripEmitter = new DripEmitter(); 28 | const foxifyEmitterV1 = new FoxifyEmitterV1(); 29 | const foxifyEmitter = new FoxifyEmitter(); 30 | 31 | for (let i = 0; i < 10; i++) { 32 | for (let j = 0; j < 10; j++) { 33 | eventEmitter.on(`event:${i}`, handle); 34 | eventEmitter2.on(`event:${i}`, handle); 35 | eventEmitter3.on(`event:${i}`, handle); 36 | dripEmitter.on(`event:${i}`, handle); 37 | foxifyEmitterV1.on(`event:${i}`, handle); 38 | foxifyEmitter.on(`event:${i}`, handle); 39 | } 40 | } 41 | 42 | new Suite() 43 | .add("events", () => { 44 | for (let i = 0; i < 10; i++) { 45 | eventEmitter.emit(`event:${i}`); 46 | } 47 | }) 48 | .add("@foxify/events", () => { 49 | for (let i = 0; i < 10; i++) { 50 | foxifyEmitter.emit(`event:${i}`); 51 | } 52 | }) 53 | .add("@foxify/events (v1)", () => { 54 | for (let i = 0; i < 10; i++) { 55 | foxifyEmitterV1.emit(`event:${i}`); 56 | } 57 | }) 58 | .add("eventemitter2", () => { 59 | for (let i = 0; i < 10; i++) { 60 | eventEmitter2.emit(`event:${i}`); 61 | } 62 | }) 63 | .add("eventemitter3", () => { 64 | for (let i = 0; i < 10; i++) { 65 | eventEmitter3.emit(`event:${i}`); 66 | } 67 | }) 68 | .add("drip", () => { 69 | for (let i = 0; i < 10; i++) { 70 | dripEmitter.emit(`event:${i}`); 71 | } 72 | }) 73 | .on("cycle", (e) => { 74 | const { name, hz, stats } = e.target; 75 | 76 | if (name === "events") base = hz; 77 | 78 | const size = stats.sample.length; 79 | 80 | stream.write([ 81 | name, 82 | `${hz.toLocaleString("en-US", { 83 | maximumFractionDigits: hz < 100 ? 2 : 0, 84 | })} ops/sec`, 85 | `${(hz / base).toLocaleString("en-US", { 86 | maximumFractionDigits: 2, 87 | })}X`, 88 | `\xb1${stats.rme.toLocaleString("en-US", { 89 | maximumFractionDigits: 2, 90 | })}%`, 91 | `${size} run${size === 1 ? "" : "s"} sampled`, 92 | ]); 93 | }) 94 | .on("complete", function onComplete() { 95 | console.log("\n> Fastest is %s", this.filter("fastest").map("name")); 96 | }) 97 | .run(); 98 | -------------------------------------------------------------------------------- /benchmarks/init.js: -------------------------------------------------------------------------------- 1 | const { createStream } = require("table"); 2 | const { Suite } = require("benchmark"); 3 | const { EventEmitter } = require("events"); 4 | const { EventEmitter2 } = require("eventemitter2"); 5 | const EventEmitter3 = require("eventemitter3"); 6 | const { EventEmitter: DripEmitter } = require("drip"); 7 | const { EventEmitter: FoxifyEmitterV1 } = require("eventsV1"); 8 | const FoxifyEmitter = require("..").default; 9 | 10 | const stream = createStream({ 11 | columnDefault: { 12 | alignment: "center", 13 | width: 20, 14 | }, 15 | columnCount: 5, 16 | }); 17 | 18 | let base; 19 | 20 | new Suite() 21 | .add("events", () => { 22 | const emitter = new EventEmitter(); 23 | }) 24 | .add("@foxify/events", () => { 25 | const emitter = new FoxifyEmitter(); 26 | }) 27 | .add("@foxify/events (v1)", () => { 28 | const emitter = new FoxifyEmitterV1(); 29 | }) 30 | .add("eventemitter2", () => { 31 | const emitter = new EventEmitter2(); 32 | }) 33 | .add("eventemitter3", () => { 34 | const emitter = new EventEmitter3(); 35 | }) 36 | .add("drip", () => { 37 | const emitter = new DripEmitter(); 38 | }) 39 | .on("cycle", (e) => { 40 | const { name, hz, stats } = e.target; 41 | 42 | if (name === "events") base = hz; 43 | 44 | const size = stats.sample.length; 45 | 46 | stream.write([ 47 | name, 48 | `${hz.toLocaleString("en-US", { 49 | maximumFractionDigits: hz < 100 ? 2 : 0, 50 | })} ops/sec`, 51 | `${(hz / base).toLocaleString("en-US", { 52 | maximumFractionDigits: 2, 53 | })}X`, 54 | `\xb1${stats.rme.toLocaleString("en-US", { 55 | maximumFractionDigits: 2, 56 | })}%`, 57 | `${size} run${size === 1 ? "" : "s"} sampled`, 58 | ]); 59 | }) 60 | .on("complete", function onComplete() { 61 | console.log("\n> Fastest is %s", this.filter("fastest").map("name")); 62 | }) 63 | .run(); 64 | -------------------------------------------------------------------------------- /benchmarks/listeners.js: -------------------------------------------------------------------------------- 1 | const { createStream } = require("table"); 2 | const { Suite } = require("benchmark"); 3 | const { EventEmitter } = require("events"); 4 | const { EventEmitter2 } = require("eventemitter2"); 5 | const EventEmitter3 = require("eventemitter3"); 6 | const { EventEmitter: DripEmitter } = require("drip"); 7 | const { EventEmitter: FoxifyEmitterV1 } = require("eventsV1"); 8 | const FoxifyEmitter = require("..").default; 9 | 10 | const stream = createStream({ 11 | columnDefault: { 12 | alignment: "center", 13 | width: 20, 14 | }, 15 | columnCount: 5, 16 | }); 17 | 18 | let base; 19 | 20 | function handle() { 21 | return 1; 22 | } 23 | 24 | const eventEmitter = new EventEmitter(); 25 | const eventEmitter2 = new EventEmitter2(); 26 | const eventEmitter3 = new EventEmitter3(); 27 | const dripEmitter = new DripEmitter(); 28 | const foxifyEmitterV1 = new FoxifyEmitterV1(); 29 | const foxifyEmitter = new FoxifyEmitter(); 30 | 31 | eventEmitter.setMaxListeners(25); 32 | eventEmitter2.setMaxListeners(25); 33 | 34 | for (let i = 0; i < 25; i++) { 35 | eventEmitter.on("foo", handle); 36 | eventEmitter2.on("foo", handle); 37 | eventEmitter3.on("foo", handle); 38 | dripEmitter.on("foo", handle); 39 | foxifyEmitterV1.on("foo", handle); 40 | foxifyEmitter.on("foo", handle); 41 | } 42 | 43 | new Suite() 44 | .add("events", () => { 45 | eventEmitter.listeners("foo"); 46 | }) 47 | .add("@foxify/events", () => { 48 | foxifyEmitter.listeners("foo"); 49 | }) 50 | .add("@foxify/events (v1)", () => { 51 | foxifyEmitterV1.listeners("foo"); 52 | }) 53 | .add("eventemitter2", () => { 54 | eventEmitter2.listeners("foo"); 55 | }) 56 | .add("eventemitter3", () => { 57 | eventEmitter3.listeners("foo"); 58 | }) 59 | .add("drip", () => { 60 | dripEmitter.listeners("foo"); 61 | }) 62 | .on("cycle", (e) => { 63 | const { name, hz, stats } = e.target; 64 | 65 | if (name === "events") base = hz; 66 | 67 | const size = stats.sample.length; 68 | 69 | stream.write([ 70 | name, 71 | `${hz.toLocaleString("en-US", { 72 | maximumFractionDigits: hz < 100 ? 2 : 0, 73 | })} ops/sec`, 74 | `${(hz / base).toLocaleString("en-US", { 75 | maximumFractionDigits: 2, 76 | })}X`, 77 | `\xb1${stats.rme.toLocaleString("en-US", { 78 | maximumFractionDigits: 2, 79 | })}%`, 80 | `${size} run${size === 1 ? "" : "s"} sampled`, 81 | ]); 82 | }) 83 | .on("complete", function onComplete() { 84 | console.log("\n> Fastest is %s", this.filter("fastest").map("name")); 85 | }) 86 | .run(); 87 | -------------------------------------------------------------------------------- /benchmarks/once.js: -------------------------------------------------------------------------------- 1 | const { createStream } = require("table"); 2 | const { Suite } = require("benchmark"); 3 | const { EventEmitter } = require("events"); 4 | const { EventEmitter2 } = require("eventemitter2"); 5 | const EventEmitter3 = require("eventemitter3"); 6 | const { EventEmitter: DripEmitter } = require("drip"); 7 | const { EventEmitter: FoxifyEmitterV1 } = require("eventsV1"); 8 | const FoxifyEmitter = require("..").default; 9 | 10 | const stream = createStream({ 11 | columnDefault: { 12 | alignment: "center", 13 | width: 20, 14 | }, 15 | columnCount: 5, 16 | }); 17 | 18 | let base; 19 | 20 | function handle() { 21 | return 1; 22 | } 23 | 24 | const eventEmitter = new EventEmitter(); 25 | const eventEmitter2 = new EventEmitter2(); 26 | const eventEmitter3 = new EventEmitter3(); 27 | const dripEmitter = new DripEmitter(); 28 | const foxifyEmitterV1 = new FoxifyEmitterV1(); 29 | const foxifyEmitter = new FoxifyEmitter(); 30 | 31 | new Suite() 32 | .add("events", () => { 33 | eventEmitter.once("foo", handle).emit("foo"); 34 | }) 35 | .add("@foxify/events", () => { 36 | foxifyEmitter.once("foo", handle).emit("foo"); 37 | }) 38 | .add("@foxify/events (v1)", () => { 39 | foxifyEmitterV1.once("foo", handle).emit("foo"); 40 | }) 41 | .add("eventemitter2", () => { 42 | eventEmitter2.once("foo", handle).emit("foo"); 43 | }) 44 | .add("eventemitter3", () => { 45 | eventEmitter3.once("foo", handle).emit("foo"); 46 | }) 47 | .add("drip", () => { 48 | dripEmitter.once("foo", handle).emit("foo"); 49 | }) 50 | .on("cycle", (e) => { 51 | const { name, hz, stats } = e.target; 52 | 53 | if (name === "events") base = hz; 54 | 55 | const size = stats.sample.length; 56 | 57 | stream.write([ 58 | name, 59 | `${hz.toLocaleString("en-US", { 60 | maximumFractionDigits: hz < 100 ? 2 : 0, 61 | })} ops/sec`, 62 | `${(hz / base).toLocaleString("en-US", { 63 | maximumFractionDigits: 2, 64 | })}X`, 65 | `\xb1${stats.rme.toLocaleString("en-US", { 66 | maximumFractionDigits: 2, 67 | })}%`, 68 | `${size} run${size === 1 ? "" : "s"} sampled`, 69 | ]); 70 | }) 71 | .on("complete", function onComplete() { 72 | console.log("\n> Fastest is %s", this.filter("fastest").map("name")); 73 | }) 74 | .run(); 75 | -------------------------------------------------------------------------------- /benchmarks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "benchmark": "^2.1.4", 5 | "drip": "^1.4.0", 6 | "eventemitter2": "^6.4.5", 7 | "eventemitter3": "^4.0.7", 8 | "eventsV1": "npm:@foxify/events@^1.1.4", 9 | "table": "^6.8.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /benchmarks/start.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ $# -eq 0 ]; then 4 | echo "usage: $0 " 5 | exit 1 6 | fi 7 | 8 | benchmark=$1 9 | 10 | echo Starting benchmark "${benchmark##*/}" 11 | echo 12 | node "$benchmark" 13 | echo 14 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property, visit: 3 | * https://jestjs.io/docs/en/configuration.html 4 | */ 5 | 6 | module.exports = { 7 | // All imported modules in your tests should be mocked automatically 8 | // automock: false, 9 | 10 | // Stop running tests after `n` failures 11 | // bail: 0, 12 | 13 | // The directory where Jest should store its cached dependency information 14 | // cacheDirectory: "/tmp/jest_rt", 15 | 16 | // Automatically clear mock calls and instances between every test 17 | // clearMocks: false, 18 | 19 | // Indicates whether the coverage information should be collected while executing the test 20 | // collectCoverage: false, 21 | 22 | // An array of glob patterns indicating a set of files for which coverage information should be collected 23 | // collectCoverageFrom: undefined, 24 | 25 | // The directory where Jest should output its coverage files 26 | // coverageDirectory: undefined, 27 | 28 | // An array of regexp pattern strings used to skip coverage collection 29 | // coveragePathIgnorePatterns: [ 30 | // "/node_modules/" 31 | // ], 32 | 33 | // Indicates which provider should be used to instrument code for coverage 34 | coverageProvider: "v8", 35 | 36 | // A list of reporter names that Jest uses when writing coverage reports 37 | // coverageReporters: [ 38 | // "json", 39 | // "text", 40 | // "lcov", 41 | // "clover" 42 | // ], 43 | 44 | // An object that configures minimum threshold enforcement for coverage results 45 | // coverageThreshold: undefined, 46 | 47 | // A path to a custom dependency extractor 48 | // dependencyExtractor: undefined, 49 | 50 | // Make calling deprecated APIs throw helpful error messages 51 | // errorOnDeprecated: false, 52 | 53 | // Force coverage collection from ignored files using an array of glob patterns 54 | // forceCoverageMatch: [], 55 | 56 | // A path to a module which exports an async function that is triggered once before all test suites 57 | // globalSetup: undefined, 58 | 59 | // A path to a module which exports an async function that is triggered once after all test suites 60 | // globalTeardown: undefined, 61 | 62 | // A set of global variables that need to be available in all test environments 63 | // globals: {}, 64 | 65 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. 66 | // maxWorkers: "50%", 67 | 68 | // An array of directory names to be searched recursively up from the requiring module's location 69 | // moduleDirectories: [ 70 | // "node_modules" 71 | // ], 72 | 73 | // An array of file extensions your modules use 74 | // moduleFileExtensions: [ 75 | // "js", 76 | // "json", 77 | // "jsx", 78 | // "ts", 79 | // "tsx", 80 | // "node" 81 | // ], 82 | 83 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module 84 | // moduleNameMapper: {}, 85 | 86 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 87 | // modulePathIgnorePatterns: [], 88 | 89 | // Activates notifications for test results 90 | // notify: false, 91 | 92 | // An enum that specifies notification mode. Requires { notify: true } 93 | // notifyMode: "failure-change", 94 | 95 | // A preset that is used as a base for Jest's configuration 96 | // preset: undefined, 97 | 98 | // Run tests from one or more projects 99 | // projects: undefined, 100 | 101 | // Use this configuration option to add custom reporters to Jest 102 | // reporters: undefined, 103 | 104 | // Automatically reset mock state between every test 105 | // resetMocks: false, 106 | 107 | // Reset the module registry before running each individual test 108 | // resetModules: false, 109 | 110 | // A path to a custom resolver 111 | // resolver: undefined, 112 | 113 | // Automatically restore mock state between every test 114 | // restoreMocks: false, 115 | 116 | // The root directory that Jest should scan for tests and modules within 117 | // rootDir: undefined, 118 | 119 | // A list of paths to directories that Jest should use to search for files in 120 | // roots: [ 121 | // "" 122 | // ], 123 | 124 | // Allows you to use a custom runner instead of Jest's default test runner 125 | // runner: "jest-runner", 126 | 127 | // The paths to modules that run some code to configure or set up the testing environment before each test 128 | // setupFiles: [], 129 | 130 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 131 | // setupFilesAfterEnv: [], 132 | 133 | // The number of seconds after which a test is considered as slow and reported as such in the results. 134 | // slowTestThreshold: 5, 135 | 136 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 137 | // snapshotSerializers: [], 138 | 139 | // The test environment that will be used for testing 140 | testEnvironment: "node", 141 | 142 | // Options that will be passed to the testEnvironment 143 | // testEnvironmentOptions: {}, 144 | 145 | // Adds a location field to test results 146 | // testLocationInResults: false, 147 | 148 | // The glob patterns Jest uses to detect test files 149 | // testMatch: [ 150 | // "**/__tests__/**/*.[jt]s?(x)", 151 | // "**/?(*.)+(spec|test).[tj]s?(x)" 152 | // ], 153 | 154 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 155 | // testPathIgnorePatterns: [ 156 | // "/node_modules/" 157 | // ], 158 | 159 | // The regexp pattern or array of patterns that Jest uses to detect test files 160 | // testRegex: [], 161 | 162 | // This option allows the use of a custom results processor 163 | // testResultsProcessor: undefined, 164 | 165 | // This option allows use of a custom test runner 166 | // testRunner: "jasmine2", 167 | 168 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href 169 | // testURL: "http://localhost", 170 | 171 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" 172 | // timers: "real", 173 | 174 | // A map from regular expressions to paths to transformers 175 | transform: { 176 | "^.+\\.ts$": "ts-jest" 177 | }, 178 | 179 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 180 | // transformIgnorePatterns: [ 181 | // "/node_modules/", 182 | // "\\.pnp\\.[^\\/]+$" 183 | // ], 184 | 185 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 186 | // unmockedModulePathPatterns: undefined, 187 | 188 | // Indicates whether each individual test should be reported during the run 189 | // verbose: undefined, 190 | 191 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 192 | // watchPathIgnorePatterns: [], 193 | 194 | // Whether to use watchman for file crawling 195 | // watchman: true, 196 | }; 197 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@foxify/events", 3 | "version": "2.1.0", 4 | "description": "A high performance EventEmitter alternative for Node.js and browser", 5 | "author": { 6 | "name": "Ardalan Amini", 7 | "email": "ardalanamini22@gmail.com", 8 | "url": "https://ardalanamini.com" 9 | }, 10 | "license": "MIT", 11 | "homepage": "https://github.com/foxifyjs/events#readme", 12 | "repository": { 13 | "type": "git", 14 | "url": "git+ssh://git@github.com/foxifyjs/events.git" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/foxifyjs/events/issues" 18 | }, 19 | "keywords": [ 20 | "event", 21 | "events", 22 | "emitter", 23 | "EventEmitter", 24 | "EventEmitter2", 25 | "EventEmitter3", 26 | "addEventListener", 27 | "addListener", 28 | "emit", 29 | "emits", 30 | "once" 31 | ], 32 | "main": "dist/index.js", 33 | "types": "dist/index.d.ts", 34 | "scripts": { 35 | "build": "tsc", 36 | "build:watch": "npm run build -- --watch", 37 | "lint": "eslint src __tests__", 38 | "lint:fix": "npm run lint -- --fix", 39 | "test": "jest", 40 | "test:coverage": "npm test -- --coverage", 41 | "test:ci": "npm run test:coverage -- --ci", 42 | "benchmarks": "find benchmarks -maxdepth 1 -name '*.js' -exec benchmarks/start.sh {} \\;" 43 | }, 44 | "devDependencies": { 45 | "@types/jest": "^27.4.0", 46 | "@types/node": "^17.0.10", 47 | "@typescript-eslint/eslint-plugin": "^5.10.0", 48 | "@typescript-eslint/parser": "^5.10.0", 49 | "eslint": "^8.7.0", 50 | "eslint-plugin-jest": "^25.7.0", 51 | "jest": "^27.4.7", 52 | "ts-jest": "^27.1.3", 53 | "typescript": "^4.5.5" 54 | }, 55 | "workspaces": [ 56 | "benchmarks" 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /src/EventEmitter.ts: -------------------------------------------------------------------------------- 1 | import Listener from "./Listener"; 2 | import type { 3 | EventTemplateT, 4 | TemplateEventT, 5 | TemplateListenerArgsT, 6 | TemplateListenerContextT, 7 | TemplateListenerT, 8 | } from "./Listener"; 9 | 10 | export default class EventEmitter